diff options
Diffstat (limited to 'src/server.c')
| -rw-r--r-- | src/server.c | 294 |
1 files changed, 278 insertions, 16 deletions
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; + } } } |