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