initial commit
This commit is contained in:
commit
d197d53ca1
|
@ -0,0 +1 @@
|
||||||
|
bin/
|
|
@ -0,0 +1,60 @@
|
||||||
|
flags = -Wall -D_GNU_SOURCE -Iinclude
|
||||||
|
lib-flags = $(flags) -fPIC -shared
|
||||||
|
obj-flags = $(flags) -c
|
||||||
|
exe-flags = $(flags)
|
||||||
|
exe-src := $(wildcard src/*.c)
|
||||||
|
exe := $(exe-src:src/%.c=bin/%)
|
||||||
|
lib-src := $(wildcard lib/*.c)
|
||||||
|
lib := $(lib-src:lib/%.c=bin/%.so)
|
||||||
|
obj := $(lib-src:lib/%.c=bin/%.o)
|
||||||
|
server-src := $(wildcard src/*.c)
|
||||||
|
client-src := $(wildcard src/client/*.c)
|
||||||
|
shared-src := $(wildcard src/shared/*.c)
|
||||||
|
|
||||||
|
all: exe lib
|
||||||
|
|
||||||
|
exe: $(exe)
|
||||||
|
|
||||||
|
lib: % : server-% client-%
|
||||||
|
|
||||||
|
server-lib client-lib: %-lib : bin/%.so
|
||||||
|
|
||||||
|
# note: $^ is all prereqs listed on the effective line, $< is first prereq listed
|
||||||
|
|
||||||
|
$(exe): bin/client.o bin/server.o
|
||||||
|
$(exe): bin/% : src/%.c
|
||||||
|
@mkdir -p $(@D)
|
||||||
|
@printf "Compiling executable $@ ...\n"
|
||||||
|
gcc $(exe-flags) $^ -o $@
|
||||||
|
@printf "Done.\n"
|
||||||
|
|
||||||
|
bin/%.o : include/%.h
|
||||||
|
bin/%.o : lib/%.c
|
||||||
|
@mkdir -p $(@D)
|
||||||
|
@printf "Compiling object for $< ...\n"
|
||||||
|
gcc $(obj-flags) $< -o $@
|
||||||
|
@printf "Done.\n"
|
||||||
|
|
||||||
|
bin/%.so : include/%.h
|
||||||
|
bin/%.so : lib/%.c
|
||||||
|
@mkdir -p $(@D)
|
||||||
|
@printf "Compiling DLL for $< ... \n"
|
||||||
|
@gcc $(lib-flags) $< -o $@
|
||||||
|
@printf "Done.\n"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@rm -rf bin/*
|
||||||
|
@printf "Object files and executable removed.\n"
|
||||||
|
|
||||||
|
# for debugging the Makefile. say e.g. "make print-shared-src"
|
||||||
|
print-% : ; @echo $* = $($*)
|
||||||
|
|
||||||
|
# client-test server-test: % : bin/%
|
||||||
|
|
||||||
|
# bin/client-test bin/server-test: c-flags += -g
|
||||||
|
# bin/client-test bin/server-test: bin/%-test : src/c/%.h
|
||||||
|
# bin/client-test bin/server-test: bin/%-test : src/c/%.c
|
||||||
|
# @printf "Compiling test executable for $< ... \n"
|
||||||
|
# @gcc $(c-flags) -o $@ $< && printf "Done.\n"
|
||||||
|
|
||||||
|
.PHONY: clean exe server-lib client-lib all
|
|
@ -0,0 +1,18 @@
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// run this before using anything else
|
||||||
|
// path: filesystem path to server's unix socket
|
||||||
|
int client_init(char* path);
|
||||||
|
// once successfully connected, send a string request
|
||||||
|
int client_request(char* request);
|
||||||
|
// if your request is successful, get the response
|
||||||
|
// the memory will be freed next time you request,
|
||||||
|
// so copy it if you need it
|
||||||
|
char* client_get_response();
|
|
@ -0,0 +1,30 @@
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
|
||||||
|
// TODO: I looked at https://man7.org/linux/man-pages/man3/cmsg.3.html to get this value, but I really don't know if it's right...
|
||||||
|
#define CONTROL_BUFFER_SIZE CMSG_SPACE(sizeof(struct ucred))
|
||||||
|
// expect clients' paths to be 6 chars long (auto-assigned sockets are a null first byte plus 5 [0-9a-f] characters)
|
||||||
|
#define CLIENT_PATH_LENGTH 6
|
||||||
|
|
||||||
|
// run this before using anything else
|
||||||
|
// path: filesystem path to create a unix socket at (can't be in use)
|
||||||
|
int server_init(char* path);
|
||||||
|
// blocks until a request is received
|
||||||
|
int server_listen();
|
||||||
|
// once a request is received, get it
|
||||||
|
// the memory will be freed next time you listen,
|
||||||
|
// so copy it if you need it
|
||||||
|
char* server_get_request();
|
||||||
|
// get the uid of the last successful request
|
||||||
|
uid_t server_get_uid();
|
||||||
|
// get the username associated with a uid
|
||||||
|
char* server_get_username(uid_t uid);
|
||||||
|
// send back a response
|
||||||
|
int server_respond(char* response);
|
|
@ -0,0 +1,103 @@
|
||||||
|
#include "client.h"
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct sockaddr_un server_addr;
|
||||||
|
socklen_t server_addr_len;
|
||||||
|
int sock_fd;
|
||||||
|
char* response;
|
||||||
|
} client_globals = {
|
||||||
|
.response = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
int client_init(char* path) {
|
||||||
|
struct sockaddr_un client_addr;
|
||||||
|
// if we specify this as the size, the socket will get a unique abstract autobind name
|
||||||
|
socklen_t client_addr_len = sizeof(sa_family_t);
|
||||||
|
|
||||||
|
client_globals.sock_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||||||
|
if (client_globals.sock_fd < 0) {
|
||||||
|
return errno; // TODO make an error code enum
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&client_addr, 0, sizeof(client_addr));
|
||||||
|
client_addr.sun_family = AF_UNIX;
|
||||||
|
|
||||||
|
if (bind(client_globals.sock_fd, (struct sockaddr *)&client_addr, client_addr_len) != 0) {
|
||||||
|
return errno; // TODO make an error code enum
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up server address for all sockets to use
|
||||||
|
memset(&client_globals.server_addr, 0, sizeof(client_globals.server_addr));
|
||||||
|
client_globals.server_addr.sun_family = AF_UNIX;
|
||||||
|
// copy in the provided directory path for the socket
|
||||||
|
memcpy(client_globals.server_addr.sun_path, path, strlen(path));
|
||||||
|
|
||||||
|
client_globals.server_addr_len = sizeof(client_globals.server_addr.sun_family) + strlen(client_globals.server_addr.sun_path);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int client_request(char* request) {
|
||||||
|
sendto(client_globals.sock_fd, request, strlen(request) + 1, 0, (struct sockaddr *) &client_globals.server_addr, client_globals.server_addr_len);
|
||||||
|
|
||||||
|
// RECEIVE
|
||||||
|
ssize_t msg_size;
|
||||||
|
|
||||||
|
{
|
||||||
|
// make copy of server address struct
|
||||||
|
struct sockaddr_un server_addr = server_addr;
|
||||||
|
// recvfrom requires a pointer to server_addr_len, so make a copy just in case it does something to it
|
||||||
|
socklen_t server_addr_len_copy = client_globals.server_addr_len;
|
||||||
|
|
||||||
|
msg_size = recvfrom(
|
||||||
|
client_globals.sock_fd,
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
MSG_PEEK | MSG_TRUNC,
|
||||||
|
(struct sockaddr *) &server_addr,
|
||||||
|
&server_addr_len_copy
|
||||||
|
);
|
||||||
|
if (msg_size < 0) {
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
free(client_globals.response);
|
||||||
|
client_globals.response = malloc(msg_size);
|
||||||
|
|
||||||
|
// make a new copy of server address struct
|
||||||
|
struct sockaddr_un server_addr = server_addr;
|
||||||
|
// recvfrom requires a pointer to server_addr_len, so make a copy just in case it does something to it
|
||||||
|
socklen_t server_addr_len_copy = client_globals.server_addr_len;
|
||||||
|
|
||||||
|
ssize_t recv_size;
|
||||||
|
recv_size = recvfrom(
|
||||||
|
client_globals.sock_fd,
|
||||||
|
client_globals.response,
|
||||||
|
msg_size,
|
||||||
|
0,
|
||||||
|
(struct sockaddr *) &server_addr,
|
||||||
|
&server_addr_len_copy
|
||||||
|
);
|
||||||
|
|
||||||
|
if (recv_size < 0) {
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* client_get_response() {
|
||||||
|
return client_globals.response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// void __attribute__ ((constructor)) init(void) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
void __attribute__ ((destructor)) client_clean_up(void) {
|
||||||
|
free(client_globals.response);
|
||||||
|
close(client_globals.sock_fd);
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
#include "server.h"
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int sock_fd;
|
||||||
|
char* request;
|
||||||
|
uid_t uid;
|
||||||
|
char* client_path;
|
||||||
|
} server_globals = {
|
||||||
|
.request = NULL,
|
||||||
|
.uid = 0,
|
||||||
|
.client_path = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
int server_init(char* path) {
|
||||||
|
umask(0);
|
||||||
|
server_globals.sock_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||||||
|
if (server_globals.sock_fd < 0) {
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_un server_addr;
|
||||||
|
|
||||||
|
memset(&server_addr, 0, sizeof(server_addr));
|
||||||
|
server_addr.sun_family = AF_UNIX;
|
||||||
|
// copy in the provided directory path for the socket
|
||||||
|
memcpy(server_addr.sun_path, path, strlen(path));
|
||||||
|
|
||||||
|
// // is this needed?
|
||||||
|
// signal(SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
|
socklen_t server_addr_len = sizeof(server_addr.sun_family) + strlen(server_addr.sun_path);
|
||||||
|
|
||||||
|
unlink(server_addr.sun_path);
|
||||||
|
if (bind(server_globals.sock_fd, (struct sockaddr *)&server_addr, server_addr_len) != 0) {
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
int optval = 1;
|
||||||
|
if (setsockopt(server_globals.sock_fd, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) != 0) {
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
server_globals.client_path = malloc(CLIENT_PATH_LENGTH);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int server_listen() {
|
||||||
|
struct sockaddr_un client_addr;
|
||||||
|
struct msghdr message_hdr;
|
||||||
|
// tells recvmsg where to put the client address name
|
||||||
|
message_hdr.msg_name = (struct sockaddr *)&client_addr;
|
||||||
|
// only ever expect the abstract address to be 5 chars long (plus one for null at beginning)
|
||||||
|
// sockaddr_un is big enough to handle up to 108 characters but we don't want more than 6
|
||||||
|
message_hdr.msg_namelen = sizeof(sa_family_t) + CLIENT_PATH_LENGTH;
|
||||||
|
|
||||||
|
// this is where the message (the client's request) will be stored, it's weird cause it can support multiple buffers
|
||||||
|
struct iovec iovec;
|
||||||
|
// the buffer to receive into is just the memory for the request in the return result, this way no copying is required
|
||||||
|
iovec.iov_base = NULL;
|
||||||
|
iovec.iov_len = 0;
|
||||||
|
message_hdr.msg_iov = &iovec;
|
||||||
|
// there's only one element in the iovec cause we only use one buffer
|
||||||
|
message_hdr.msg_iovlen = 1;
|
||||||
|
|
||||||
|
// set after call, not checked so it shouldn't matter
|
||||||
|
message_hdr.msg_flags = 0;
|
||||||
|
|
||||||
|
// this is the buffer for getting ancillary data (namely, the UID of the requesting client)
|
||||||
|
char message_control_buffer[CONTROL_BUFFER_SIZE];
|
||||||
|
message_hdr.msg_control = &message_control_buffer;
|
||||||
|
message_hdr.msg_controllen = CONTROL_BUFFER_SIZE;
|
||||||
|
|
||||||
|
// call gets info about waiting message and sender (address and uid)
|
||||||
|
ssize_t msg_size = recvmsg(server_globals.sock_fd, &message_hdr, MSG_PEEK | MSG_TRUNC);
|
||||||
|
|
||||||
|
// for now, just output how big it was, later, we will check to make sure it's the correct size
|
||||||
|
if (msg_size < 0) {
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract credentials from the message
|
||||||
|
struct ucred credentials;
|
||||||
|
memcpy(&credentials, CMSG_DATA((struct cmsghdr *)message_hdr.msg_control), sizeof(credentials));
|
||||||
|
|
||||||
|
// recvmsg populated msg_name with the client's actual path info.
|
||||||
|
// copy that into the client path
|
||||||
|
// client_addr.sun_path should be updated by recvmsg
|
||||||
|
memcpy(server_globals.client_path, client_addr.sun_path, CLIENT_PATH_LENGTH);
|
||||||
|
|
||||||
|
server_globals.uid = credentials.uid;
|
||||||
|
// username = getpwuid(credentials.uid)->pw_name;
|
||||||
|
|
||||||
|
// allocate buffer to receive the actual message
|
||||||
|
free(server_globals.request);
|
||||||
|
server_globals.request = malloc(msg_size);
|
||||||
|
|
||||||
|
ssize_t recv_size = recv(server_globals.sock_fd, server_globals.request, msg_size, 0);
|
||||||
|
|
||||||
|
if (recv_size < 0) {
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
// request was already added to result, so it's ready to go
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int server_respond(char* response) {
|
||||||
|
// set up client address
|
||||||
|
struct sockaddr_un client_addr;
|
||||||
|
client_addr.sun_family = AF_UNIX;
|
||||||
|
memcpy(client_addr.sun_path, server_globals.client_path, CLIENT_PATH_LENGTH);
|
||||||
|
|
||||||
|
// send stream
|
||||||
|
// NOTE: I added a 1 here to include the null byte at the end of the string, because it didn't seem to be included otherwise
|
||||||
|
// but it appears to work without that when the client sends the request? Probably needs more investigation
|
||||||
|
ssize_t sent = sendto(server_globals.sock_fd, response, strlen(response) + 1, 0, (struct sockaddr *)&client_addr, sizeof(sa_family_t) + CLIENT_PATH_LENGTH);
|
||||||
|
|
||||||
|
if (sent < 0) {
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* server_get_request() {
|
||||||
|
return server_globals.request;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* server_get_username(uid_t uid) {
|
||||||
|
// if this doesn't work sometime you can try storing the uid instead and then calling getpwuid in here
|
||||||
|
return getpwuid(uid)->pw_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
uid_t server_get_uid() {
|
||||||
|
return server_globals.uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// void __attribute__ ((constructor)) init(void) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
void __attribute__ ((destructor)) server_clean_up(void) {
|
||||||
|
// TODO: error handling?
|
||||||
|
close(server_globals.sock_fd);
|
||||||
|
free(server_globals.request);
|
||||||
|
free(server_globals.client_path);
|
||||||
|
// don't need to free username since it's provided by getpwuid
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Local Server
|
||||||
|
|
||||||
|
You could use this to make a little game on your server or something.
|
||||||
|
|
||||||
|
The server library listens on a [Unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket) you specify (like a file path), and then other users can send requests to the server using the client library.
|
||||||
|
|
||||||
|
The server library can tell you what user sent a request, giving you some nice OS-level authentication.
|
||||||
|
|
||||||
|
There are also WIP command line utilities that should make it easier to work with the libraries from scripts.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
#### Make the libraries
|
||||||
|
|
||||||
|
```
|
||||||
|
> make lib
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Make the command line util
|
||||||
|
|
||||||
|
```
|
||||||
|
> make exe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Using the libraries
|
||||||
|
|
||||||
|
To use the libraries, you'll need the library file created from the `make lib` step and the corresponding header file.
|
||||||
|
|
||||||
|
So, for example, to use the server, you'd need:
|
||||||
|
- `./bin/server.so`
|
||||||
|
- `./include/server.h`
|
||||||
|
|
||||||
|
You should be able to use these libraries in a variety of languages that support a C [foreign function interface (FFI)](https://en.wikipedia.org/wiki/Foreign_function_interface).
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
I'll write up API documentation soon, but for now, look at the header files in `./include` for an idea of the API.
|
||||||
|
|
||||||
|
### Running the command line util
|
||||||
|
|
||||||
|
There are subcommands for the server and the client respectively.
|
||||||
|
This part is still a WIP, so don't expect much yet!
|
||||||
|
|
||||||
|
##### Server
|
||||||
|
|
||||||
|
```
|
||||||
|
> localserv server <socket-path>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `<socket-path>` can be any file path that you have permissions to that isn't already occupied, e.g., `~/example.sock`
|
||||||
|
|
||||||
|
##### Client
|
||||||
|
|
||||||
|
```
|
||||||
|
> localserv client <socket-path> <request>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `<socket-path>` is the same one you specified when starting your server.
|
||||||
|
The `<request>` is just any string you want to send to the server.
|
|
@ -0,0 +1,84 @@
|
||||||
|
#include "server.h"
|
||||||
|
#include "client.h"
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
char *exe_name;
|
||||||
|
|
||||||
|
int server(char *path) {
|
||||||
|
int error;
|
||||||
|
error = server_init(path);
|
||||||
|
if (error) {
|
||||||
|
fprintf(stderr, "%s: server init failed: %s\n", exe_name, strerror(error));
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = server_listen();
|
||||||
|
if (error) {
|
||||||
|
fprintf(stderr, "%s: listen failed: %s\n", exe_name, strerror(error));
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* request = server_get_request();
|
||||||
|
printf("Request: %s\n", request);
|
||||||
|
printf("Input response: ");
|
||||||
|
|
||||||
|
char* response = NULL;
|
||||||
|
size_t len = 0;
|
||||||
|
if (getline(&response, &len, stdin) == -1) {
|
||||||
|
fprintf(stderr, "%s: failed getting input: %s\n", exe_name, strerror(errno));
|
||||||
|
}
|
||||||
|
error = server_respond(response);
|
||||||
|
if (error) {
|
||||||
|
fprintf(stderr, "%s: respond failed: %s\n", exe_name, strerror(error));
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int client(char *path, char *message) {
|
||||||
|
int error;
|
||||||
|
error = client_init(path);
|
||||||
|
if (error) {
|
||||||
|
fprintf(stderr, "%s: client init failed: %s\n", exe_name, strerror(error));
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = client_request(message);
|
||||||
|
if (error) {
|
||||||
|
fprintf(stderr, "%s: request failed: %s\n", exe_name, strerror(error));
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%s\n", client_get_response());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
exe_name = argv[0];
|
||||||
|
if (argc <= 1) {
|
||||||
|
// TODO: full help
|
||||||
|
fprintf(stderr, "%s: provide subcommand \"server\" or \"client\"", exe_name);
|
||||||
|
return -1;
|
||||||
|
} else if (strcmp(argv[1], "server") != 0 && strcmp(argv[1], "client") != 0) {
|
||||||
|
// TODO: full help
|
||||||
|
fprintf(stderr, "%s: command not recognized; provide subcommand \"server\" or \"client\"", exe_name);
|
||||||
|
return -1;
|
||||||
|
} else if (argc < 3) {
|
||||||
|
fprintf(stderr, "%s: provide a path", exe_name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (strcmp(argv[1], "server") == 0) {
|
||||||
|
return server(argv[2]);
|
||||||
|
} else if (strcmp(argv[1], "client") == 0) {
|
||||||
|
if (argc < 4) {
|
||||||
|
fprintf(stderr, "%s client: provide a message", exe_name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return client(argv[2], argv[3]);
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue