diff options
| author | Franklin Wei <git@fwei.tk> | 2015-12-07 17:48:32 -0500 |
|---|---|---|
| committer | Franklin Wei <git@fwei.tk> | 2015-12-07 17:48:32 -0500 |
| commit | 0f3bd99c40594c46b5b4bb0603085ec9cc111e0e (patch) | |
| tree | 0533aa2d245a8bc8aaa652c8c4c039c27504f0f9 | |
| parent | 8bc78787a99efdf4c64032d7fe55905ff995cd98 (diff) | |
| download | netcosm-0f3bd99c40594c46b5b4bb0603085ec9cc111e0e.zip netcosm-0f3bd99c40594c46b5b4bb0603085ec9cc111e0e.tar.gz netcosm-0f3bd99c40594c46b5b4bb0603085ec9cc111e0e.tar.bz2 netcosm-0f3bd99c40594c46b5b4bb0603085ec9cc111e0e.tar.xz | |
child-parent communication
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | src/auth.c | 49 | ||||
| -rw-r--r-- | src/client.c | 84 | ||||
| -rw-r--r-- | src/netcosm.h | 25 | ||||
| -rw-r--r-- | src/server.c | 294 | ||||
| -rw-r--r-- | src/telnet.c | 20 |
6 files changed, 436 insertions, 38 deletions
@@ -2,7 +2,7 @@ CC = gcc OUT = build PLATFORM = unix -NETCOSM_OBJ = src/server.o src/client.o src/auth.o src/telnet.o +NETCOSM_OBJ = src/server.o src/client.o src/auth.o src/telnet.o src/util.o CFLAGS = -O3 -g -I src/ -I target/$(PLATFORM) -Wall -Wextra LDFLAGS = -lgcrypt @@ -123,7 +123,7 @@ static int remove_user_internal(const char *user, int *found, char **filename) bool auth_remove(const char *user2) { char *user = strdup(user2); - strtok(user, "\r\n"); + remove_cruft(user); if(valid_login_name(user)) { int found = 0; @@ -151,9 +151,9 @@ bool auth_remove(const char *user2) bool add_change_user(const char *user2, const char *pass2, int level) { char *user = strdup(user2); - strtok(user, "\r\n"); + remove_cruft(user); char *pass = strdup(pass2); - strtok(pass, "\r\n"); + remove_cruft(pass); printf("Add user '%s'\n", user); @@ -213,7 +213,7 @@ void first_run_setup(void) printf("Admin account name: "); fflush(stdout); getline(&admin_name, &len, stdin); - strtok(admin_name, "\r\n"); + remove_cruft(admin_name); } while(!valid_login_name(admin_name)); printf("Admin password (_DO_NOT_ USE A VALUABLE PASSWORD): "); @@ -221,7 +221,7 @@ void first_run_setup(void) char *admin_pass = NULL; len = 0; getline(&admin_pass, &len, stdin); - strtok(admin_pass, "\r\n"); + remove_cruft(admin_pass); if(!add_change_user(admin_name, admin_pass, PRIV_ADMIN)) error("Unknown error"); @@ -238,8 +238,8 @@ struct authinfo_t auth_check(const char *name2, const char *pass2) /* get our own copy to remove newlines */ char *name = strdup(name2); char *pass = strdup(pass2); - strtok(name, "\r\n"); - strtok(pass, "\r\n"); + remove_cruft(name); + remove_cruft(pass); /* find it in the user list */ @@ -255,6 +255,7 @@ struct authinfo_t auth_check(const char *name2, const char *pass2) while(1) { char *line = NULL; + char *save; size_t len = 0; if(getline(&line, &len, f) < 0) { @@ -264,14 +265,14 @@ struct authinfo_t auth_check(const char *name2, const char *pass2) free(pass); goto bad; } - if(!strcmp(strtok(line, ":\r\n"), name)) + if(!strcmp(strtok_r(line, ":\r\n", &save), name)) { free(name); - char *salt = strdup(strtok(NULL, ":\r\n")); - char *hash = strdup(strtok(NULL, ":\r\n")); + char *salt = strdup(strtok_r(NULL, ":\r\n", &save)); + char *hash = strdup(strtok_r(NULL, ":\r\n", &save)); - ret.authlevel = strtol(strtok(NULL, ":\r\n"), NULL, 10); + ret.authlevel = strtol(strtok_r(NULL, ":\r\n", &save), NULL, 10); free(line); @@ -314,3 +315,29 @@ bad: printf("Failed authentication.\n"); return ret; } + +void auth_list_users(void) +{ + FILE *f = fopen(USERFILE, "r"); + + flock(fileno(f), LOCK_SH); + + while(1) + { + char *line = NULL; + char *save; + size_t len = 0; + if(getline(&line, &len, f) < 0) + { + free(line); + return; + } + char *user = strdup(strtok_r(line, ":\r\n", &save)); + strtok_r(NULL, ":\r\n", &save); + strtok_r(NULL, ":\r\n", &save); + int priv = strtol(strtok_r(NULL, ":\r\n", &save), NULL, 0); + out("User %s priv %d\n", user, priv); + free(user); + free(line); + } +} diff --git a/src/client.c b/src/client.c index 18c390c..4ed944c 100644 --- a/src/client.c +++ b/src/client.c @@ -1,6 +1,11 @@ #include "netcosm.h" -int client_fd; +int client_fd, to_parent, from_parent; + +void out_raw(const unsigned char *buf, size_t len) +{ + write(client_fd, buf, len); +} void __attribute__((format(printf,1,2))) out(const char *fmt, ...) { @@ -8,22 +13,20 @@ void __attribute__((format(printf,1,2))) out(const char *fmt, ...) memset(buf, 0, sizeof(buf)); va_list ap; va_start(ap, fmt); - vsnprintf(buf, sizeof(buf), fmt, ap); + int len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); - write(client_fd, buf, sizeof(buf)); + out_raw((unsigned char*)buf, len); } -void out_raw(const unsigned char *buf, size_t len) -{ - write(client_fd, buf, len); -} #define BUFSZ 128 char *client_read(void) { char *buf; + tryagain: + buf = malloc(BUFSZ); memset(buf, 0, BUFSZ); if(read(client_fd, buf, BUFSZ - 1) < 0) @@ -55,11 +58,40 @@ void all_upper(char *s) } } +void sigusr2_handler(int s) +{ + (void) s; + unsigned char buf[MSG_MAX + 1]; + size_t len = read(from_parent, buf, MSG_MAX); + buf[MSG_MAX] = '\0'; + out_raw(buf, len); +} + +void client_change_state(int state) +{ + unsigned char cmdcode = REQ_CHANGESTATE; + write(to_parent, &cmdcode, sizeof(cmdcode)); + write(to_parent, &state, sizeof(state)); + kill(getppid(), SIGUSR1); +} + +void client_change_user(const char *user) +{ + unsigned char cmdcode = REQ_CHANGEUSER; + write(to_parent, &cmdcode, sizeof(cmdcode)); + write(to_parent, user, strlen(user) + 1); + kill(getppid(), SIGUSR1); +} + #define WSPACE " \t\r\n" -void client_main(int fd, struct sockaddr_in *addr, int total) +void client_main(int fd, struct sockaddr_in *addr, int total, int to, int from) { client_fd = fd; + to_parent = to; + from_parent = from; + + signal(SIGUSR2, sigusr2_handler); telnet_init(); @@ -81,13 +113,20 @@ void client_main(int fd, struct sockaddr_in *addr, int total) char *current_user; + client_change_state(STATE_AUTH); + /* auth loop */ while(1) { out("login: "); current_user = client_read(); + remove_cruft(current_user); + telnet_echo_off(); out("Password: "); char *pass = client_read(); + telnet_echo_on(); + out("\n"); + client_change_state(STATE_CHECKING); struct authinfo_t auth = auth_check(current_user, pass); memset(pass, 0, strlen(pass)); free(pass); @@ -95,11 +134,13 @@ void client_main(int fd, struct sockaddr_in *addr, int total) authlevel = auth.authlevel; if(auth.success) { + client_change_state(STATE_LOGGEDIN); out("Access Granted.\n\n"); break; } else { + client_change_state(STATE_FAILED); free(current_user); out("Access Denied.\n\n"); if(++failures >= MAX_FAILURES) @@ -112,8 +153,12 @@ void client_main(int fd, struct sockaddr_in *addr, int total) return; bool admin = (authlevel == PRIV_ADMIN); + if(admin) + client_change_state(STATE_ADMIN); /* authenticated */ + printf("Authenticated as %s\n", current_user); + client_change_user(current_user); while(1) { out(">> "); @@ -123,6 +168,8 @@ void client_main(int fd, struct sockaddr_in *addr, int total) char *tok = strtok_r(cmd, WSPACE, &save); + if(!tok) + continue; all_upper(tok); if(admin) @@ -183,14 +230,33 @@ void client_main(int fd, struct sockaddr_in *addr, int total) else out("Usage: USER ADD|CHANGE <USERNAME>\n"); } + else if(!strcmp(what, "LIST")) + { + auth_list_users(); + } + } + else if(!strcmp(tok, "CLIENTS")) + { + unsigned char cmd_code = REQ_LISTCLIENTS; + write(to_parent, &cmd_code, sizeof(cmd_code)); + kill(getppid(), SIGUSR1); + waitpid(-1, NULL, 0); } } - if(!strcmp(tok, "QUIT")) + if(!strcmp(tok, "QUIT") || !strcmp(tok, "EXIT")) { free(cmd); goto done; } + else if(!strcmp(tok, "SAY")) + { + char *what = strtok_r(NULL, "", &save); + unsigned char cmd_code = REQ_BCASTMSG; + write(to_parent, &cmd_code, sizeof(cmd_code)); + dprintf(to_parent, "%s says %s", current_user, what); + kill(getppid(), SIGUSR1); + } next_cmd: diff --git a/src/netcosm.h b/src/netcosm.h index be6d1a2..f9f0fc4 100644 --- a/src/netcosm.h +++ b/src/netcosm.h @@ -12,6 +12,8 @@ #include <stdlib.h> #include <string.h> #include <sys/file.h> +#include <sys/ipc.h> +#include <sys/mman.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/wait.h> @@ -34,7 +36,7 @@ struct authinfo_t { int authlevel; }; -void client_main(int fd, struct sockaddr_in *addr, int); +void client_main(int sock, struct sockaddr_in *addr, int, int to_parent, int from_parent); void __attribute__((noreturn)) error(const char *fmt, ...); void first_run_setup(void); struct authinfo_t auth_check(const char*, const char*); @@ -48,3 +50,24 @@ void telnet_handle_command(const unsigned char*); void out(const char *fmt, ...) __attribute__((format(printf,1,2))); void out_raw(const unsigned char*, size_t); void telnet_init(void); +void telnet_echo_on(void); +void telnet_echo_off(void); +#define MSG_MAX 512 + +void remove_cruft(char*); + +/* child->master commands */ +#define REQ_INVALID 0 +#define REQ_BCASTMSG 1 +#define REQ_LISTCLIENTS 2 +#define REQ_CHANGESTATE 3 +#define REQ_CHANGEUSER 4 + +#define STATE_INIT 0 +#define STATE_AUTH 1 +#define STATE_CHECKING 2 +#define STATE_LOGGEDIN 3 +#define STATE_ADMIN 4 +#define STATE_FAILED 5 + +void auth_list_users(void); diff --git a/src/server.c b/src/server.c index 39719eb..8e2c4a1 100644 --- a/src/server.c +++ b/src/server.c @@ -33,16 +33,51 @@ void __attribute__((noreturn)) error(const char *fmt, ...) exit(EXIT_FAILURE); } -int num_clients = 0; +/* assume int is atomic */ +volatile int num_clients = 0; -void sigchld_handler(int s) +struct child_data { + pid_t pid; + int readpipe[2]; + int outpipe[2]; + + int state; + char *user; + + struct in_addr addr; + + /* a linked list works well for this because no random-access is needed */ + struct child_data *next; +} *child_data; + +void sigchld_handler(int s, siginfo_t *info, void *vp) { (void) s; - printf("Client disconnect.\n"); + (void) info; + (void) vp; + const char *msg = "Client disconnect.\n"; + write(STDOUT_FILENO, msg, strlen(msg)); // waitpid() might overwrite errno, so we save and restore it: int saved_errno = errno; - while(waitpid(-1, NULL, WNOHANG) > 0); + pid_t pid; + while((pid = waitpid(-1, NULL, WNOHANG)) > 0) + { + struct child_data *iter = child_data, *last = NULL; + while(iter) + { + if(iter->pid == pid) + { + if(!last) + child_data = iter->next; + else + last->next = iter->next; + free(iter); + break; + } + iter = iter->next; + } + } errno = saved_errno; @@ -51,27 +86,212 @@ void sigchld_handler(int s) int port; -void handle_client(int fd, struct sockaddr_in *addr, int num_clients) +void handle_client(int fd, struct sockaddr_in *addr, + int num_clients, int to, int from) { - client_main(fd, addr, num_clients); + client_main(fd, addr, num_clients, to, from); } int server_socket; void serv_cleanup(void) { - printf("Shutdown server.\n"); + write(STDOUT_FILENO, "Shutdown server.\n", strlen("Shutdown server.\n")); if(shutdown(server_socket, SHUT_RDWR) > 0) error("shutdown"); close(server_socket); } -void sigint_handler(int sig) +void sigint_handler(int s) { - (void) sig; + (void) s; serv_cleanup(); } +void write_client(int child_pipe, struct child_data *iter) +{ + char buf[128]; + printf("writing ip\n"); + char *ip = inet_ntoa(iter->addr); + int len = snprintf(buf, sizeof(buf), "Client %s PID %d\n", ip, iter->pid); + write(child_pipe, buf, len); +} + +void req_pass_msg(unsigned char *data, size_t datalen, + struct child_data *sender, struct child_data *child) +{ + (void) sender; + + write(child->outpipe[1], data, datalen); + kill(child->pid, SIGUSR2); +} + +void req_send_clientinfo(unsigned char *data, size_t datalen, + struct child_data *sender, struct child_data *child) +{ + (void) data; + (void) datalen; + char buf[128]; + int len; + const char *state[] = { + "INIT", + "LOGIN SCREEN", + "CHECKING CREDENTIALS", + "LOGGED IN AS USER", + "LOGGED IN AS ADMIN", + "ACCESS DENIED", + }; + if(sender->user) + len = snprintf(buf, sizeof(buf), "Client %s PID %d [%s] USER %s\n", + inet_ntoa(child->addr), child->pid, state[child->state], child->user); + else + len = snprintf(buf, sizeof(buf), "Client %s PID %d [%s]\n", + inet_ntoa(child->addr), child->pid, state[child->state]); + write(sender->outpipe[1], buf, len); +} + +void req_signal_sender(struct child_data *sender) +{ + kill(sender->pid, SIGUSR2); +} + +void req_change_state(unsigned char *data, size_t datalen, + struct child_data *sender, struct child_data *child) +{ + (void) child; + if(datalen == sizeof(sender->state)) + { + sender->state = *((int*)data); + } + else + printf("State data is of the wrong size.\n"); +} + +void req_change_user(unsigned char *data, size_t datalen, + struct child_data *sender, struct child_data *child) +{ + if(sender->user) + free(sender->user); + sender->user = strdup((char*)data); +} + +static const struct child_request { + unsigned char code; + + bool havedata; + + enum { CHILD_SENDER, CHILD_ALL_BUT_SENDER, CHILD_ALL } which; + + /* sender_pipe is the pipe to the sender of the request */ + /* data is bogus if havedata = false */ + void (*handle_child)(unsigned char *data, size_t len, + struct child_data *sender, struct child_data *child); + + void (*finalize)(struct child_data *sender); +} requests[] = { + { REQ_BCASTMSG, true, CHILD_ALL_BUT_SENDER, req_pass_msg, NULL }, + { REQ_LISTCLIENTS, false, CHILD_ALL, req_send_clientinfo, req_signal_sender }, + { REQ_CHANGESTATE, true, CHILD_SENDER, req_change_state, NULL }, + { REQ_CHANGEUSER, true, CHILD_SENDER, req_change_user, NULL }, +}; + + +/* SIGUSR1 is used by children to communicate with the master process */ +/* the master handles commands that involve multiple children, i.e. message passing, listing clients, etc. */ +void sigusr1_handler(int s, siginfo_t *info, void *vp) +{ + (void) s; + (void) vp; + pid_t sender_pid = info->si_pid; + printf("PID %d requests a broadcast message\n", sender_pid); + + unsigned char cmd, data[MSG_MAX + 1]; + const struct child_request *req = NULL; + size_t datalen = 0; + struct child_data *sender = NULL; + + /* we have to iterate over the linked list twice */ + /* first to get the data, second to send it to all the children */ + + struct child_data *iter = child_data; + + while(iter) + { + if(!req) + { + if(iter->pid == sender_pid) + { + sender = iter; + read(iter->readpipe[0], &cmd, 1); + for(unsigned int i = 0; i < ARRAYLEN(requests); ++i) + { + if(cmd == requests[i].code) + { + req = requests + i; + break; + } + } + if(!req) + { + printf("Unknown request.\n"); + return; + } + + printf("Got command %d\n", cmd); + if(req->havedata) + { + datalen = read(iter->readpipe[0], data, sizeof(data)); + } + + switch(req->which) + { + case CHILD_SENDER: + case CHILD_ALL: + req->handle_child(data, datalen, sender, iter); + if(req->which == CHILD_SENDER) + goto finish; + break; + default: + break; + } + } + } + else + { + switch(req->which) + { + case CHILD_ALL: + case CHILD_ALL_BUT_SENDER: + req->handle_child(data, datalen, sender, iter); + break; + default: + break; + } + } + iter = iter->next; + } + + /* iterate over the rest of the children, if needed */ + if(req && req->which != CHILD_SENDER) + { + iter = child_data; + while(iter) + { + if(iter->pid == sender_pid) + break; + + req->handle_child(data, datalen, sender, iter); + + iter = iter->next; + } + } + +finish: + + if(req->finalize) + req->finalize(sender); +} + int main(int argc, char *argv[]) { if(argc != 2) @@ -103,21 +323,36 @@ int main(int argc, char *argv[]) server_socket = sock; + /* set up signal handlers for SIGCHLD, SIGUSR1, and SIGINT */ + /* SIGUSR1 is used for broadcast signalling */ struct sigaction sa; - sa.sa_handler = sigchld_handler; // reap all dead processes + sa.sa_sigaction = sigchld_handler; // reap all dead processes sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_SIGINFO; + if (sigaction(SIGCHLD, &sa, NULL) < 0) + error("sigaction"); + + sa.sa_handler = sigint_handler; sa.sa_flags = SA_RESTART; - if (sigaction(SIGCHLD, &sa, NULL) == -1) { - perror("sigaction"); - exit(1); - } + if(sigaction(SIGINT, &sa, NULL) < 0) + error("sigaction"); + if(sigaction(SIGTERM, &sa, NULL) < 0) + error("sigaction"); - signal(SIGINT, sigint_handler); + sa.sa_sigaction = sigusr1_handler; + sa.sa_flags = SA_RESTART | SA_SIGINFO; + if(sigaction(SIGUSR1, &sa, NULL) < 0) + error("sigaction"); if(access(USERFILE, F_OK) < 0) first_run_setup(); + if(access(USERFILE, R_OK | W_OK) < 0) + error("cannot access "USERFILE); + + child_data = NULL; + printf("Listening on port %d\n", port); while(1) @@ -130,21 +365,48 @@ int main(int argc, char *argv[]) ++num_clients; + int readpipe[2]; /* child->parent */ + int outpipe [2]; /* parent->child */ + + if(pipe(readpipe) < 0) + error("pipe"); + if(pipe(outpipe) < 0) + error("pipe"); + pid_t pid = fork(); if(pid < 0) error("fork"); if(!pid) { + close(readpipe[0]); + close(outpipe[1]); close(sock); + printf("Child with PID %d spawned\n", getpid()); + server_socket = new_sock; - handle_client(new_sock, &client, num_clients); + handle_client(new_sock, &client, num_clients, readpipe[1], outpipe[0]); exit(0); } else + { + close(readpipe[1]); + close(outpipe[0]); close(new_sock); + + /* add the child to the child list */ + struct child_data *old = child_data; + child_data = malloc(sizeof(struct child_data)); + memcpy(child_data->outpipe, outpipe, sizeof(outpipe)); + memcpy(child_data->readpipe, readpipe, sizeof(readpipe)); + child_data->addr = client.sin_addr; + child_data->next = old; + child_data->pid = pid; + child_data->state = STATE_INIT; + child_data->user = NULL; + } } } diff --git a/src/telnet.c b/src/telnet.c index 2e86195..1838b45 100644 --- a/src/telnet.c +++ b/src/telnet.c @@ -47,6 +47,24 @@ void telnet_handle_command(const unsigned char *buf) printf("\n"); } +void telnet_echo_off(void) +{ + const unsigned char seq[] = { + IAC, DONT, ECHO, + IAC, WILL, ECHO, + }; + out_raw(seq, ARRAYLEN(seq)); +} + +void telnet_echo_on(void) +{ + const unsigned char seq[] = { + IAC, DO, ECHO, + IAC, WONT, ECHO, + }; + out_raw(seq, ARRAYLEN(seq)); +} + void telnet_init(void) { const unsigned char init_seq[] = { @@ -56,6 +74,8 @@ void telnet_init(void) IAC, DONT, NAWS, IAC, WONT, STATUS, IAC, DONT, STATUS, + IAC, DO, ECHO, + IAC, WONT, ECHO, }; out_raw(init_seq, ARRAYLEN(init_seq)); } |