aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFranklin Wei <git@fwei.tk>2015-12-07 17:48:32 -0500
committerFranklin Wei <git@fwei.tk>2015-12-07 17:48:32 -0500
commit0f3bd99c40594c46b5b4bb0603085ec9cc111e0e (patch)
tree0533aa2d245a8bc8aaa652c8c4c039c27504f0f9
parent8bc78787a99efdf4c64032d7fe55905ff995cd98 (diff)
downloadnetcosm-0f3bd99c40594c46b5b4bb0603085ec9cc111e0e.zip
netcosm-0f3bd99c40594c46b5b4bb0603085ec9cc111e0e.tar.gz
netcosm-0f3bd99c40594c46b5b4bb0603085ec9cc111e0e.tar.bz2
netcosm-0f3bd99c40594c46b5b4bb0603085ec9cc111e0e.tar.xz
child-parent communication
-rw-r--r--Makefile2
-rw-r--r--src/auth.c49
-rw-r--r--src/client.c84
-rw-r--r--src/netcosm.h25
-rw-r--r--src/server.c294
-rw-r--r--src/telnet.c20
6 files changed, 436 insertions, 38 deletions
diff --git a/Makefile b/Makefile
index e80da2f..5f28c9a 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/src/auth.c b/src/auth.c
index b54d0a4..dcc89d8 100644
--- a/src/auth.c
+++ b/src/auth.c
@@ -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));
}