diff options
| author | Franklin Wei <git@fwei.tk> | 2016-01-22 17:03:05 -0500 |
|---|---|---|
| committer | Franklin Wei <git@fwei.tk> | 2016-01-22 17:03:05 -0500 |
| commit | b33d9c81c116d43b38ceb8b247bd77f9736574b9 (patch) | |
| tree | b92a923ffc29ee438c646f4b2b08f5bd588dee2d /src | |
| parent | a486d4b5e1679e0ddf5a4afa661501afefe4a854 (diff) | |
| download | netcosm-b33d9c81c116d43b38ceb8b247bd77f9736574b9.zip netcosm-b33d9c81c116d43b38ceb8b247bd77f9736574b9.tar.gz netcosm-b33d9c81c116d43b38ceb8b247bd77f9736574b9.tar.bz2 netcosm-b33d9c81c116d43b38ceb8b247bd77f9736574b9.tar.xz | |
PuTTY compatibility, lots of other stuff
Diffstat (limited to 'src')
| -rw-r--r-- | src/client.c | 64 | ||||
| -rw-r--r-- | src/hash.h | 4 | ||||
| -rw-r--r-- | src/room.c | 59 | ||||
| -rw-r--r-- | src/room.h | 49 | ||||
| -rw-r--r-- | src/server.c | 58 | ||||
| -rw-r--r-- | src/server_reqs.c | 33 | ||||
| -rw-r--r-- | src/server_reqs.h | 1 | ||||
| -rw-r--r-- | src/telnet.c | 106 | ||||
| -rw-r--r-- | src/telnet.h | 23 |
9 files changed, 185 insertions, 212 deletions
diff --git a/src/client.c b/src/client.c index 2ad7256..0cfb0f9 100644 --- a/src/client.c +++ b/src/client.c @@ -72,9 +72,10 @@ void __attribute__((format(printf,1,2))) out(const char *fmt, ...) /* do some line wrapping */ int pos = 0, last_space = 0; - char newline = '\n'; char *ptr = buf; - uint16_t line_width = telnet_get_width(); + uint16_t line_width = telnet_get_width() + 1; + char *line_buf = malloc(line_width + 2); + size_t line_idx = 0; while(ptr[pos]) { bool is_newline = (ptr[pos] == '\n'); @@ -82,12 +83,21 @@ void __attribute__((format(printf,1,2))) out(const char *fmt, ...) { if(is_newline || !last_space) last_space = pos; + while(*ptr && last_space-- > 0) - out_raw(ptr++, 1); - out_raw(&newline, 1); + { + line_buf[line_idx++] = *ptr++; + } + + line_buf[line_idx++] = '\r'; + line_buf[line_idx++] = '\n'; + + out_raw(line_buf, line_idx); + line_idx = 0; + if(is_newline) - ++ptr; /* skip newline */ - while(*ptr && *ptr == ' ') + ++ptr; /* skip the newline */ + while(*ptr == ' ') ++ptr; last_space = 0; pos = 0; @@ -100,6 +110,7 @@ void __attribute__((format(printf,1,2))) out(const char *fmt, ...) } } out_raw(ptr, strlen(ptr)); + free(line_buf); } static volatile sig_atomic_t request_complete; @@ -155,6 +166,8 @@ void send_master(unsigned char cmd, const void *data, size_t sz) while(!request_complete) poll_requests(); free(req); + + debugf("done with request\n"); } #define BUFSZ 128 @@ -162,10 +175,12 @@ void send_master(unsigned char cmd, const void *data, size_t sz) char *client_read(void) { char *buf; - + size_t bufidx; tryagain: buf = malloc(BUFSZ); + bufidx = 0; + memset(buf, 0, BUFSZ); /* set of the client fd and the pipe from our parent */ @@ -193,20 +208,27 @@ tryagain: } else if(fds[i].fd == client_fd) { - ssize_t len = read(client_fd, buf, BUFSZ - 1); + ssize_t len = read(client_fd, buf + bufidx, BUFSZ - bufidx - 1); if(len < 0) error("lost connection"); buf[BUFSZ - 1] = '\0'; - enum telnet_status ret = telnet_parse_data((unsigned char*)buf, len); + enum telnet_status ret = telnet_parse_data((unsigned char*)buf + bufidx, len); - if(ret != TELNET_DATA) + switch(ret) { + case TELNET_EXIT: + case TELNET_FOUNDCMD: free(buf); if(ret == TELNET_EXIT) exit(0); goto tryagain; + case TELNET_DATA: + bufidx += len; + continue; + case TELNET_LINEOVER: + break; } remove_cruft(buf); @@ -268,6 +290,8 @@ bool poll_requests(void) unsigned char cmd = packet[0]; + debugf("Child gets code %d\n", cmd); + switch(cmd) { case REQ_BCASTMSG: @@ -348,6 +372,8 @@ void client_change_room(room_id id) send_master(REQ_SETROOM, &id, sizeof(id)); } +void *dir_map = NULL; + void client_move(const char *dir) { const struct dir_pair { @@ -377,14 +403,13 @@ void client_move(const char *dir) { "IN", DIR_IN }, { "OUT", DIR_OT }, }; - static void *map = NULL; - if(!map) + if(!dir_map) { - map = hash_init(ARRAYLEN(dirs), hash_djb, compare_strings); - hash_insert_pairs(map, (struct hash_pair*)dirs, sizeof(struct dir_pair), ARRAYLEN(dirs)); + dir_map = hash_init(ARRAYLEN(dirs), hash_djb, compare_strings); + hash_insert_pairs(dir_map, (struct hash_pair*)dirs, sizeof(struct dir_pair), ARRAYLEN(dirs)); } - struct dir_pair *pair = hash_lookup(map, dir); + struct dir_pair *pair = hash_lookup(dir_map, dir); if(pair) { send_master(REQ_MOVE, &pair->val, sizeof(pair->val)); @@ -416,6 +441,8 @@ void client_main(int fd, struct sockaddr_in *addr, int total, int to, int from) debugf("New client %s\n", ip); debugf("Total clients: %d\n", total); + debugf("client is running with uid %d\n", getuid()); + auth: out("NetCosm " NETCOSM_VERSION "\n"); @@ -601,8 +628,15 @@ auth: else if(!strcmp(what, "KICK")) { char *pid_s = strtok_r(NULL, WSPACE, &save); + all_upper(pid_s); if(pid_s) { + if(!strcmp(pid_s, "ALL")) + { + const char *msg = "Kicking everyone...\n"; + send_master(REQ_KICKALL, msg, strlen(msg)); + goto next_cmd; + } /* weird pointer voodoo */ /* TODO: simplify */ char pidbuf[MAX(sizeof(pid_t), MSG_MAX)]; @@ -41,8 +41,8 @@ void hash_setfreekey_cb(void*, void (*cb)(void *key)); void hash_free(void*); /* - * insert a pair, returns NULL if not already found, otherwise returns - * the existing data pointer + * insert a pair, returns NULL if NOT already found, otherwise returns + * the existing data pointer without inserting the new pair */ void *hash_insert(void*, const void *key, const void *data); @@ -21,8 +21,10 @@ #include "hash.h" #include "server.h" #include "room.h" +#include "userdb.h" /* processed world data */ + static struct room_t *world; static size_t world_sz; static char *world_name; @@ -40,51 +42,17 @@ bool room_user_add(room_id id, struct child_data *child) if(!room) error("unknown room %d", id); - struct user_t *iter = room->users, *last = NULL; - while(iter) - { - if(iter->data->pid == child->pid) - return false; - last = iter; - iter = iter->next; - } - - struct user_t *new = calloc(sizeof(struct user_t), 1); - - new->data = child; - new->next = NULL; - - if(last) - last->next = new; + if(hash_insert(room->users, &child->pid, child)) + return false; else - room->users = new; - - ++room->num_users; - - return true; + return true; } bool room_user_del(room_id id, struct child_data *child) { struct room_t *room = room_get(id); - struct user_t *iter = room->users, *last = NULL; - while(iter) - { - if(iter->data->pid == child->pid) - { - if(last) - last->next = iter->next; - else - room->users = iter->next; - free(iter); - --room->num_users; - return true; - } - last = iter; - iter = iter->next; - } - return false; + return hash_remove(room->users, &child->pid); } void write_roomid(int fd, room_id *id) @@ -143,12 +111,8 @@ void world_save(const char *fname) static void room_free(struct room_t *room) { - while(room->users) - { - struct user_t *old = room->users; - room->users = room->users->next; - free(old); - } + hash_free(room->users); + room->users = NULL; free(room->data.name); free(room->data.desc); } @@ -168,6 +132,9 @@ void world_free(void) } } +static SIMP_HASH(pid_t, pid_hash); +static SIMP_EQUAL(pid_t, pid_equal); + /** * Loads a world using data on disk and in memory. * @@ -205,6 +172,8 @@ bool world_load(const char *fname, const struct roomdata_t *data, size_t data_sz for(unsigned i = 0; i < world_sz; ++i) { + world[i].users = hash_init((userdb_size() + 1) / 2, pid_hash, pid_equal); + world[i].id = read_roomid(fd); memcpy(&world[i].data, data + i, sizeof(struct roomdata_t)); world[i].data.name = read_string(fd); @@ -255,6 +224,8 @@ void world_init(const struct roomdata_t *data, size_t sz, const char *name) } } + world[i].users = hash_init((userdb_size() + 1) / 2, pid_hash, pid_equal); + world_sz = i + 1; } @@ -18,16 +18,18 @@ #pragma once +/* Our world is an array of rooms, each having a list of objects in + them, as well as actions that can be performed in the room. Objects + are added by hooks in rooms, which are provided by the world + module. */ + typedef enum room_id { ROOM_NONE = -1 } room_id; typedef unsigned __int128 obj_id; -enum direction_t { DIR_N = 0, DIR_NE, DIR_E, DIR_SE, DIR_S, DIR_SW, DIR_W, DIR_NW, DIR_UP, DIR_DN, DIR_IN, DIR_OT, NUM_DIRECTIONS }; +typedef struct child_data user_t; -struct user_t { - struct child_data *data; - struct user_t *next; -}; +enum direction_t { DIR_N = 0, DIR_NE, DIR_E, DIR_SE, DIR_S, DIR_SW, DIR_W, DIR_NW, DIR_UP, DIR_DN, DIR_IN, DIR_OT, NUM_DIRECTIONS }; /* the data we get from a world module */ struct roomdata_t { @@ -41,30 +43,33 @@ struct roomdata_t { const char * const adjacent[NUM_DIRECTIONS]; void (* const hook_init)(room_id id); - void (* const hook_enter)(room_id room, struct user_t *user); - void (* const hook_leave)(room_id room, struct user_t *user); + void (* const hook_enter)(room_id room, user_t *user); + void (* const hook_leave)(room_id room, user_t *user); }; struct object_t { obj_id id; - const char *class; + const char *name; /* no articles: "a", "an", "the" */ - bool proper; /* whether to use "the" in describing this object */ void *userdata; + bool can_take; + bool list; + void (*hook_serialize)(int fd, struct object_t*); - void (*hook_take)(struct object_t*, struct user_t *user); - void (*hook_drop)(struct object_t*, struct user_t *user); - void (*hook_use)(struct object_t*, struct user_t *user); + void (*hook_take)(struct object_t*, user_t *user); + void (*hook_drop)(struct object_t*, user_t *user); + void (*hook_use)(struct object_t*, user_t *user); void (*hook_destroy)(struct object_t*); + char* (*hook_desc)(struct object_t*, user_t*); }; struct verb_t { const char *name; /* toks is strtok_r's pointer */ - void (*execute)(const char *toks, struct user_t *user); + void (*execute)(const char *toks, user_t *user); }; struct room_t { @@ -73,16 +78,10 @@ struct room_t { room_id adjacent[NUM_DIRECTIONS]; - /* arrays instead of linked lists because insertion should be rare for these */ - size_t objects_sz; - struct object_t *objects; - - size_t verbs_sz; - struct verb_t *verbs; - - /* linked list for users, random access is rare */ - struct user_t *users; - int num_users; + /* hash maps */ + void *objects; + void *verbs; + void *users; /* PID -> user_t */ }; /* room/world */ @@ -94,6 +93,10 @@ struct room_t *room_get(room_id id); bool room_user_add(room_id id, struct child_data *child); bool room_user_del(room_id id, struct child_data *child); +/* returns a new object */ struct object_t *obj_new(void); +/* new should point to a statically allocated object */ +void obj_add(room_id room, struct object_t *new); + void world_free(void); diff --git a/src/server.c b/src/server.c index 4430f7e..6ce4de4 100644 --- a/src/server.c +++ b/src/server.c @@ -103,28 +103,10 @@ static void handle_client(int fd, struct sockaddr_in *addr, static void __attribute__((noreturn)) serv_cleanup(void) { - debugf("Shutdown server.\n"); - - /* kill all our children (usually init claims them and wait()'s - for them, but not always) */ - if(child_map) - { - struct sigaction sa; - sigfillset(&sa.sa_mask); - sigaddset(&sa.sa_mask, SIGCHLD); - sa.sa_handler = SIG_IGN; - sa.sa_flags = 0; - sigaction(SIGCHLD, &sa, NULL); /* kill all children */ - void *ptr = child_map, *save; - do { - struct child_data *child = hash_iterate(ptr, &save, NULL); - if(!child) - break; - ptr = NULL; - kill(child->pid, SIGKILL); - } while(1); - handle_disconnects(); - } + if(!are_child) + debugf("Shutdown server.\n"); + else + debugf("Shutdown worker.\n"); if(shutdown(server_socket, SHUT_RDWR) > 0) error("shutdown"); @@ -136,6 +118,10 @@ static void __attribute__((noreturn)) serv_cleanup(void) hash_free(child_map); child_map = NULL; + extern void *dir_map; + hash_free(dir_map); + dir_map = NULL; + userdb_shutdown(); extern char *current_user; @@ -150,7 +136,7 @@ static void __attribute__((noreturn)) serv_cleanup(void) static void __attribute__((noreturn)) sigint_handler(int s) { (void) s; - serv_cleanup(); + exit(0); } static void check_userfile(void) @@ -208,9 +194,6 @@ static int server_bind(void) return sock; } -static SIMP_HASH(pid_t, pid_hash); -static SIMP_EQUAL(pid_t, pid_equal); - static void childreq_cb(EV_P_ ev_io *w, int revents) { (void) EV_A; @@ -337,6 +320,9 @@ static void init_signals(void) error("sigaction"); } +static SIMP_HASH(pid_t, pid_hash); +static SIMP_EQUAL(pid_t, pid_equal); + int server_main(int argc, char *argv[]) { debugf("*** Starting NetCosm %s (libev %d.%d, %s) ***\n", @@ -353,6 +339,7 @@ int server_main(int argc, char *argv[]) srand(time(0)); server_socket = server_bind(); + userdb_init(USERFILE); check_userfile(); @@ -374,28 +361,13 @@ int server_main(int argc, char *argv[]) * because libev grabs SIGCHLD */ init_signals(); - /* drop root privileges */ - if(getuid() == 0) - { - struct passwd *nobody = getpwnam("nobody"); - if(!nobody) - error("couldn't get unprivileged user"); - if(setgid(nobody->pw_gid) != 0) - error("setgid"); - if(setuid(nobody->pw_uid) != 0) - error("setuid"); - if(setuid(0) >= 0) - error("failed to drop root"); - if(access(USERFILE, R_OK) >= 0) - error("failed to drop root"); - debugf("Dropped root privileges.\n"); - } - ev_io server_watcher; ev_io_init(&server_watcher, new_connection_cb, server_socket, EV_READ); ev_set_priority(&server_watcher, EV_MAXPRI); ev_io_start(EV_A_ &server_watcher); + atexit(serv_cleanup); + ev_loop(loop, 0); /* should never get here */ diff --git a/src/server_reqs.c b/src/server_reqs.c index c7e9f09..bf661b9 100644 --- a/src/server_reqs.c +++ b/src/server_reqs.c @@ -30,7 +30,13 @@ static void send_packet(struct child_data *child, unsigned char cmd, pkt[0] = cmd; if(datalen) memcpy(pkt + 1, data, datalen); - write(child->outpipe[1], pkt, datalen + 1); +tryagain: + if(write(child->outpipe[1], pkt, datalen + 1) < 0) + { + /* write can fail, so we try again */ + if(errno == EAGAIN) + goto tryagain; + } } static void req_pass_msg(unsigned char *data, size_t datalen, @@ -217,6 +223,13 @@ static void req_send_geninfo(unsigned char *data, size_t datalen, struct child_d send_packet(sender, REQ_BCASTMSG, buf, len); } +static void req_kick_always(unsigned char *data, size_t datalen, + struct child_data *sender, struct child_data *child) +{ + (void) sender; + send_packet(child, REQ_KICK, data, datalen); +} + static const struct child_request { unsigned char code; @@ -245,6 +258,7 @@ static const struct child_request { { REQ_GETUSERDATA, true, CHILD_NONE, NULL, req_send_user, }, { REQ_DELUSERDATA, true, CHILD_NONE, NULL, req_del_user, }, { REQ_ADDUSERDATA, true, CHILD_NONE, NULL, req_add_user, }, + { REQ_KICKALL, true, CHILD_ALL_BUT_SENDER, req_kick_always, NULL }, //{ REQ_ROOMMSG, true, CHILD_ALL, req_send_room_msg, NULL, }, }; @@ -289,6 +303,8 @@ bool handle_child_req(int in_fd) ssize_t packet_len = read(in_fd, packet, MSG_MAX); + struct child_data *sender = NULL; + if(packet_len <= 0) goto fail; @@ -296,7 +312,7 @@ bool handle_child_req(int in_fd) memcpy(&sender_pid, packet, sizeof(pid_t)); debugf("servreq: Got request from PID %d\n", sender_pid); - struct child_data *sender = hash_lookup(child_map, &sender_pid); + sender = hash_lookup(child_map, &sender_pid); if(!sender) { @@ -343,6 +359,8 @@ bool handle_child_req(int in_fd) if(child->pid == sender->pid) continue; + debugf("iterating over child %d\n", child->pid); + switch(req->which) { case CHILD_ALL: @@ -356,12 +374,19 @@ bool handle_child_req(int in_fd) finish: + debugf("finalizing request\n"); + if(req && req->finalize) req->finalize(data, datalen, sender); - if(req) + /* fall through */ +fail: + if(sender) + { send_packet(sender, REQ_ALLDONE, NULL, 0); -fail: + debugf("sending all done code\n"); + } + return true; } diff --git a/src/server_reqs.h b/src/server_reqs.h index 06e3f46..6ab093b 100644 --- a/src/server_reqs.h +++ b/src/server_reqs.h @@ -37,6 +37,7 @@ #define REQ_ADDUSERDATA 15 /* server: insert user data; child: success/fail */ #define REQ_PRINTNEWLINE 16 /* child: print a newline */ #define REQ_ALLDONE 17 /* child: break out of send_master() */ +#define REQ_KICKALL 18 /* server: kick everyone except the sender */ /* child states, sent as an int to the master */ #define STATE_INIT 0 /* initial state */ diff --git a/src/telnet.c b/src/telnet.c index c605c2e..9963007 100644 --- a/src/telnet.c +++ b/src/telnet.c @@ -19,6 +19,8 @@ #include "globals.h" #include "client.h" + +#define TELCMDS #include "telnet.h" static uint16_t term_width, term_height; @@ -38,6 +40,7 @@ enum telnet_status telnet_parse_data(const unsigned char *buf, size_t buflen) bool iac = false; bool found_cmd = false; bool in_sb = false; + bool line_done = false; debugf("telnet: "); @@ -45,67 +48,50 @@ enum telnet_status telnet_parse_data(const unsigned char *buf, size_t buflen) { unsigned char c = buf[i]; - const struct telnet_cmd { - int val; - const char *name; - } commands[] = { - { IAC, "IAC" }, - { DONT, "DONT" }, - { DO, "DO" }, - { WONT, "WONT" }, - { WILL, "WILL" }, - { GA, "GA" }, - { AYT, "AYT" }, - { NOP, "NOP" }, - { SB, "SB" }, - { SE, "SE" }, - { ECHO, "ECHO" }, - { SGA, "SGA" }, - { STATUS, "STATUS" }, - { NAWS, "NAWS" }, - { IP, "IP" }, - }; - if(c == IAC) iac = true; + else if(c == '\n' || c == '\r') + { + debugf("found newline, done reading.\n"); + line_done = true; + } if(iac) { - for(unsigned int cmd_idx = 0; cmd_idx < ARRAYLEN(commands); ++cmd_idx) + if(TELCMD_OK(c)) { - if(c == commands[cmd_idx].val) + debugf("%s ", TELCMD(c)); + found_cmd = true; + switch(c) { - debugf("%s ", commands[cmd_idx].name); - found_cmd = true; - switch(c) + case IP: + return TELNET_EXIT; + case SB: + in_sb = true; + break; + case TELOPT_NAWS: + if(in_sb) { - case IP: - return TELNET_EXIT; - case SB: - in_sb = true; - break; - case NAWS: - if(in_sb) + /* read height/width */ + uint8_t bytes[4]; + int j = 0; + while(j < 4 && i < buflen) { - /* read height/width */ - uint8_t bytes[4]; - int j = 0; - while(j < 4 && i < buflen) + bytes[j++] = buf[++i]; + debugf("%d ", buf[j - 1]); + if(bytes[j - 1] == 255) /* 255 is doubled to distinguish from IAC */ { - bytes[j++] = buf[++i]; - debugf("%d ", buf[j - 1]); - if(bytes[j - 1] == 255) /* 255 is doubled to distinguish from IAC */ - { - ++i; - } + ++i; } - term_width = ntohs(*((uint16_t*)bytes)); - term_height = ntohs(*((uint16_t*)(bytes+2))); } - break; + if(i >= buflen && j != 4) + error("client SB NAWS command to short"); + term_width = ntohs(*((uint16_t*)bytes)); + term_height = ntohs(*((uint16_t*)(bytes+2))); } - goto got_cmd; + break; } + goto got_cmd; } } debugf("%d ", c); @@ -114,15 +100,17 @@ enum telnet_status telnet_parse_data(const unsigned char *buf, size_t buflen) } debugf("\n"); + if(found_cmd) + debugf("telnet: is NOT data\n"); - return found_cmd ? TELNET_FOUNDCMD : TELNET_DATA; + return found_cmd ? TELNET_FOUNDCMD : + (line_done ? TELNET_LINEOVER : TELNET_DATA); } void telnet_echo_off(void) { const unsigned char seq[] = { - IAC, DONT, ECHO, - IAC, WILL, ECHO, + IAC, WILL, TELOPT_ECHO, }; out_raw(seq, ARRAYLEN(seq)); } @@ -130,8 +118,7 @@ void telnet_echo_off(void) void telnet_echo_on(void) { const unsigned char seq[] = { - IAC, DO, ECHO, - IAC, WONT, ECHO, + IAC, WONT, TELOPT_ECHO, }; out_raw(seq, ARRAYLEN(seq)); } @@ -139,18 +126,17 @@ void telnet_echo_on(void) void telnet_init(void) { const unsigned char init_seq[] = { - IAC, WONT, SGA, - IAC, DONT, SGA, + IAC, WONT, TELOPT_SGA, + IAC, DONT, TELOPT_SGA, - IAC, DO, NAWS, + IAC, DO, TELOPT_NAWS, - IAC, WONT, STATUS, - IAC, DONT, STATUS, + IAC, WONT, TELOPT_STATUS, + IAC, DONT, TELOPT_STATUS, - IAC, DO, ECHO, - IAC, WONT, ECHO, + IAC, DO, TELOPT_ECHO, - IAC, DONT, LINEMODE, + IAC, DONT, TELOPT_LINEMODE, }; term_width = 80; term_height = 24; diff --git a/src/telnet.h b/src/telnet.h index 2289e84..8669e87 100644 --- a/src/telnet.h +++ b/src/telnet.h @@ -18,32 +18,13 @@ #pragma once -/* commands */ -#define IAC 255 -#define DONT 254 -#define DO 253 -#define WONT 252 -#define WILL 251 -#define SB 250 -#define GA 249 -#define EL 248 -#define EC 247 -#define AYT 246 -#define IP 244 -#define NOP 241 -#define SE 240 - -/* options */ -#define ECHO 1 -#define SGA 3 -#define STATUS 5 -#define NAWS 31 -#define LINEMODE 34 +#include <arpa/telnet.h> void telnet_init(void); enum telnet_status { TELNET_DATA = 0, TELNET_FOUNDCMD, + TELNET_LINEOVER, TELNET_EXIT }; enum telnet_status telnet_parse_data(const unsigned char*, size_t); |