From 1ba28c43fadadcad8208bd2b37c6a7614c0ed9c3 Mon Sep 17 00:00:00 2001 From: Hugo Landau Date: Wed, 19 Feb 2014 15:47:41 +0000 Subject: [PATCH] In-Flight Upgrade System, for integration. The IFU system uses JSON to serialize and deserialize ircd state to disk, re-execute itself (possibly with a newer binary) and pick up the fds which are carried across the exec() call. This allows the ircd to be upgraded without interrupting current connections. Currently, listening fds are not preserved across exec(), which means that users in the accept() queue and not yet accepted by the ircd might experience some slight turbulence. This patch must be used with current libmowgli-2, which has patches to make its epoll/kqueue and DNS resolver socket fds close-on-exec. Failure to do so will result in fd leakage of two descriptors per upgrade. To initiate an upgrade, become an operator and run /UPGRADE. The working directory from which the ircd was started must be writable. The upgrade.json serialization file is currently not deleted after the upgrade is completed to aid development. This should be changed at a later date to enhance privacy. RDNS queries should be reissued after an upgrade for clients which did not complete the RDNS phase prior to the upgrade. This may result in some duplicated queries issued to configured resolvers. The restoration process now occurs after the configuration file is loaded. The configuration file stored on disk MUST match that held in memory by the current ircd before upgrading. This is not currently enforced or checked; operator beware. If an upgrade fails during the serialization phase, the ircd continues operating. If an upgrade fails during the restoration phase (which is highly unlikely), the ircd closes all fds and re-executes itself in normal mode. This will disconnect all users but allow the ircd to continue with new connections. This code has been tested with multiple servers. The sendq serialization system has been load-tested with the /LIST output of a major network. Signed-off-by: Hugo Landau --- include/chan.h | 2 + include/conn.h | 7 ++ include/cookie.h | 2 + include/ircd.h | 4 + include/link.h | 10 ++ include/ratelimit.h | 2 + include/sendq.h | 3 + include/server.h | 8 +- include/upgrade.h | 26 ++--- include/user.h | 12 +- include/util.h | 125 +++++++++++++++++++++ modules/core/Makefile | 1 + modules/core/upgrade.c | 27 +++++ src/chan.c | 292 ++++++++++++++++++++++++++++++++++++++++++++++++- src/conn.c | 126 +++++++++++++++++++++ src/cookie.c | 23 ++++ src/link.c | 182 ++++++++++++++++++++++++++++-- src/main.c | 25 ++++- src/numeric.tab | 2 + src/ratelimit.c | 30 +++++ src/sendq.c | 73 +++++++++++++ src/server.c | 199 +++++++++++++++++++++++++++++++-- src/upgrade.c | 243 ++++++++++++++++++++++++++++++---------- src/user.c | 265 ++++++++++++++++++++++++++++++++++++++++++-- src/util.c | 193 ++++++++++++++++++++++++++++++++ 25 files changed, 1766 insertions(+), 116 deletions(-) create mode 100644 modules/core/upgrade.c diff --git a/include/chan.h b/include/chan.h index f9b7b0e..c73c4f4 100644 --- a/include/chan.h +++ b/include/chan.h @@ -118,5 +118,7 @@ extern u_chan *u_find_forward(u_chan*, u_user*, char *key); extern int u_is_muted(u_chanuser*); extern int init_chan(void); +extern int dump_chan(void); +extern int restore_chan(void); #endif diff --git a/include/conn.h b/include/conn.h index dbf8676..9ce396e 100644 --- a/include/conn.h +++ b/include/conn.h @@ -89,4 +89,11 @@ extern void u_conn_run(mowgli_eventloop_t *ev); extern int init_conn(void); +extern mowgli_json_t *u_conn_to_json(u_conn *conn); +extern u_conn *u_conn_from_json( + mowgli_eventloop_t *ev, + u_conn_ctx *ctx, + void *priv, + mowgli_json_t *jc); + #endif diff --git a/include/cookie.h b/include/cookie.h index ce52230..9eef118 100644 --- a/include/cookie.h +++ b/include/cookie.h @@ -17,5 +17,7 @@ extern void u_cookie_reset(u_cookie*); extern void u_cookie_inc(u_cookie*); extern void u_cookie_cpy(u_cookie *A, u_cookie *B); extern int u_cookie_cmp(u_cookie*, u_cookie*); +extern mowgli_json_t *u_cookie_to_json(u_cookie*); +extern int u_cookie_from_json(mowgli_json_t *, u_cookie*); #endif diff --git a/include/ircd.h b/include/ircd.h index 90ccb3f..e748ca9 100644 --- a/include/ircd.h +++ b/include/ircd.h @@ -60,6 +60,7 @@ typedef unsigned long u_ts_t; #define memberp(base, offs) ((void*)((ulong)(base) + (ulong)(offs))) #define member(mtype, base, offs) (*((mtype*)memberp(base, offs))) +#define arraylen(x) (sizeof(x)/sizeof((x)[0])) #define _stringify(x) #x #define stringify(x) _stringify(x) @@ -103,4 +104,7 @@ extern char startedstr[256]; extern char *crypt(const char*, const char*); +extern char *main_argv0; +extern short opt_port; + #endif diff --git a/include/link.h b/include/link.h index 1a30850..20a6a40 100644 --- a/include/link.h +++ b/include/link.h @@ -49,6 +49,12 @@ struct u_link { uchar ibuf[IBUFSIZE+1]; size_t ibuflen; + /* This indicates that X bytes should be skipped in ibuf when serializing + * ibuf to do an upgrade. This is necessary to prevent the UPGRADE command + * itself from re-executing after the upgrade. + */ + size_t ibufskip; + u_cookie ck_sendto; }; @@ -64,9 +70,13 @@ extern void u_link_f(u_link *link, const char *fmt, ...); extern void u_link_vnum(u_link *link, const char *tgt, int num, va_list va); extern int u_link_num(u_link *link, int num, ...); +extern void u_link_flush_input(u_link *link); extern u_link_origin *u_link_origin_create(mowgli_eventloop_t*, short); extern int init_link(void); +extern mowgli_json_t *u_link_to_json(u_link *link); +extern u_link *u_link_from_json(mowgli_json_t *j); + #endif diff --git a/include/ratelimit.h b/include/ratelimit.h index 648d2ec..0ac2c28 100644 --- a/include/ratelimit.h +++ b/include/ratelimit.h @@ -64,5 +64,7 @@ void u_ratelimit_init(u_user *user); bool u_ratelimit_allow(u_user *user, u_ratelimit_cmd_t *deduct, const char *cmd); void u_ratelimit_who_credit(u_user *user); void u_ratelimit_who_deduct(u_user *user); +mowgli_json_t *u_ratelimit_to_json(u_ratelimit_t *limit); +int u_ratelimit_from_json(mowgli_json_t *jrl, u_ratelimit_t *limit); #endif diff --git a/include/sendq.h b/include/sendq.h index 37ef23e..64b7625 100644 --- a/include/sendq.h +++ b/include/sendq.h @@ -26,4 +26,7 @@ extern size_t u_sendq_end_buffer(u_sendq*, size_t sz); extern int u_sendq_write(u_sendq*, int fd); +extern mowgli_json_t *u_sendq_to_json(u_sendq *sq); +extern int u_sendq_from_json(mowgli_json_t *sjq, u_sendq *sq); + #endif diff --git a/include/server.h b/include/server.h index 71cc6fe..d005172 100644 --- a/include/server.h +++ b/include/server.h @@ -69,8 +69,8 @@ extern mowgli_list_t my_motd; extern mowgli_list_t my_admininfo; extern char my_net_name[MAXNETNAME+1]; -extern u_server *u_server_by_sid(char *sid); -extern u_server *u_server_by_name(char *name); +extern u_server *u_server_by_sid(const char *sid); +extern u_server *u_server_by_name(const char *name); extern u_server *u_server_find(char *str); static inline u_server *u_server_by_ref(u_link *link, char *ref) @@ -92,6 +92,10 @@ extern void u_server_burst_1(u_link*, u_link_block*); extern void u_server_burst_2(u_server*, u_link_block*); extern void u_server_eob(u_server*); +extern void u_server_flush_inputs(void); + extern int init_server(void); +extern int dump_server(void); +extern int restore_server(void); #endif diff --git a/include/upgrade.h b/include/upgrade.h index 4e63b18..daa7d59 100644 --- a/include/upgrade.h +++ b/include/upgrade.h @@ -7,24 +7,16 @@ #ifndef __INC_UPGRADE_H__ #define __INC_UPGRADE_H__ -typedef struct u_udb u_udb; - -typedef void (u_udb_save_hook_t)(u_udb*); -typedef void (u_udb_load_hook_t)(u_udb*); - -extern mowgli_list_t *u_udb_save_hooks; -extern mowgli_patricia_t *u_udb_load_hooks; - -/* save functions: */ -extern void u_udb_row_start(u_udb*, char *name); -extern void u_udb_row_end(u_udb*); -extern void u_udb_put_s(u_udb*, char *s); -extern void u_udb_put_i(u_udb*, long n); - -/* load functions: */ -extern char *u_udb_get_s(u_udb*); -extern long u_udb_get_i(u_udb*); +extern const char *opt_upgrade; +extern mowgli_json_t *upgrade_json; extern int init_upgrade(void); +extern int begin_upgrade(void); +extern int finish_upgrade(void); +extern void abort_upgrade(void); + +#define UPGRADE_FILENAME "upgrade.json" +#define HOOK_UPGRADE_DUMP "upgrade:dump" +#define HOOK_UPGRADE_RESTORE "upgrade:restore" #endif diff --git a/include/user.h b/include/user.h index d71e4e3..7ee7d70 100644 --- a/include/user.h +++ b/include/user.h @@ -95,10 +95,10 @@ extern void u_user_destroy(u_user*); extern void u_user_try_register(u_user*); -extern u_user *u_user_by_nick_raw(char*); -extern u_user *u_user_by_nick(char*); -extern u_user *u_user_by_uid_raw(char*); -extern u_user *u_user_by_uid(char*); +extern u_user *u_user_by_nick_raw(const char*); +extern u_user *u_user_by_nick(const char*); +extern u_user *u_user_by_uid_raw(const char*); +extern u_user *u_user_by_uid(const char*); static inline u_user *u_user_by_ref(u_link *link, char *ref) { @@ -123,6 +123,10 @@ extern void u_user_welcome(u_user*); extern void u_user_make_euid(u_user*, char *buf); /* sizeof(buf) > 512 */ +extern void u_user_flush_inputs(void); + extern int init_user(void); +extern int dump_user(void); +extern int restore_user(void); #endif diff --git a/include/util.h b/include/util.h index 1872bfb..f0e6a51 100644 --- a/include/util.h +++ b/include/util.h @@ -129,4 +129,129 @@ static inline void str_upper(char *ch) str_transform(ch, &toupper); } +static inline void json_oseto(mowgli_json_t *obj, const char *k, mowgli_json_t *v) +{ + mowgli_json_object_add(obj, k, v ? v : mowgli_json_null); +} + +static inline void json_oseti(mowgli_json_t *obj, const char *k, int v) +{ + json_oseto(obj, k, mowgli_json_create_integer(v)); +} + +static inline void json_osetb(mowgli_json_t *obj, const char *k, bool v) +{ + json_oseto(obj, k, v ? mowgli_json_true : mowgli_json_false); +} + +#define json_osetu json_oseti /* XXX */ +#define json_oseti64 json_oseti /* XXX */ + +static inline void json_osets(mowgli_json_t *obj, const char *k, const char *v) +{ + json_oseto(obj, k, v ? mowgli_json_create_string(v) : mowgli_json_null); +} + +extern void json_osetb64(mowgli_json_t *obj, const char *k, const void *buf, size_t buf_len); +extern ssize_t json_ogetb64(mowgli_json_t *obj, const char *k, void *buf, size_t buf_len); + +static inline void json_append(mowgli_json_t *arr, mowgli_json_t *v) +{ + mowgli_json_array_add(arr, v); +} + +static inline mowgli_json_t *json_ogeto(mowgli_json_t *obj, const char *k) +{ + mowgli_json_t *v; + if (MOWGLI_JSON_TAG(obj) != MOWGLI_JSON_TAG_OBJECT) + return NULL; + v = mowgli_json_object_retrieve(obj, k); + if (!v || MOWGLI_JSON_TAG(v) == MOWGLI_JSON_TAG_NULL) + return NULL; + return v; +} + +static inline mowgli_json_t *json_ogeto_c(mowgli_json_t *obj, const char *k) +{ + mowgli_json_t *v = json_ogeto(obj, k); + if (MOWGLI_JSON_TAG(obj) != MOWGLI_JSON_TAG_OBJECT) + return NULL; + return v; +} + +static inline mowgli_string_t *json_ogets(mowgli_json_t *obj, const char *k) +{ + mowgli_json_t *js; + js = json_ogeto(obj, k); + if (!js || MOWGLI_JSON_TAG(js) != MOWGLI_JSON_TAG_STRING) + return NULL; + return MOWGLI_JSON_STRING(js); +} + +static inline mowgli_list_t *json_ogeta(mowgli_json_t *obj, const char *k) +{ + mowgli_json_t *js; + js = json_ogeto(obj, k); + if (!js || MOWGLI_JSON_TAG(js) != MOWGLI_JSON_TAG_ARRAY) + return NULL; + return MOWGLI_JSON_ARRAY(js); +} + +static inline bool json_ogeti(mowgli_json_t *obj, const char *k, int *v) +{ + mowgli_json_t *ji; + ji = json_ogeto(obj, k); + if (!ji || MOWGLI_JSON_TAG(ji) != MOWGLI_JSON_TAG_INTEGER) + return false; + *v = MOWGLI_JSON_INTEGER(ji); + return true; +} + +static inline bool json_ogeti64(mowgli_json_t *obj, const char *k, int64_t *v) +{ + int ok; + int vv = 0; + /* XXX */ + ok = json_ogeti(obj, k, &vv); + if (ok) + *v = vv; + return ok; +} + +static inline bool json_ogetu64(mowgli_json_t *obj, const char *k, uint64_t *v) +{ + return json_ogeti64(obj, k, (int64_t*)v); +} + +static inline bool json_ogetu(mowgli_json_t *obj, const char *k, unsigned *v) +{ + /* XXX */ + return json_ogeti(obj, k, (int*)v); +} + +static inline bool json_ogetb(mowgli_json_t *obj, const char *k) +{ + mowgli_json_t *jb = json_ogeto(obj, k); + + /* strict equality */ + if (MOWGLI_JSON_TAG(jb) != MOWGLI_JSON_TAG_BOOLEAN) + return false; + + return MOWGLI_JSON_BOOLEAN(jb); +} + +extern int set_cloexec(int fd); + +inline static size_t base64_inflate_size(size_t len) { + return 4*((len+2)/3); +} + +inline static size_t base64_deflate_size(size_t len) { + return ((len/4)*3)+3; +} + +extern size_t base64_encode(const void *in_buf, size_t in_buf_len, char *out_buf, char *out_cur); +extern size_t base64_decode(const char *in_buf, size_t in_buf_len, void *out_buf); + +/* vim: set noet: */ #endif diff --git a/modules/core/Makefile b/modules/core/Makefile index 055de70..e1c97dd 100644 --- a/modules/core/Makefile +++ b/modules/core/Makefile @@ -34,6 +34,7 @@ SRCS = \ tb.c \ ts6init.c \ topic.c \ + upgrade.c \ user.c \ userhost.c \ who.c \ diff --git a/modules/core/upgrade.c b/modules/core/upgrade.c new file mode 100644 index 0000000..7e1d135 --- /dev/null +++ b/modules/core/upgrade.c @@ -0,0 +1,27 @@ +/* Tethys, core/upgrade -- UPGRADE command + Copyright (C) 2014 Alex Iadicicco + Copyright (C) 2014 Hugo Landau + + This file is protected under the terms contained + in the COPYING file in the project root */ + +#include "ircd.h" + +static int c_a_upgrade(u_sourceinfo *si, u_msg *msg) +{ + int rc; + u_user_num(si->u, RPL_UPGRADESTARTING); + begin_upgrade(); + u_user_num(si->u, RPL_UPGRADEFAILED); + return 0; +} + +static u_cmd upgrade_cmdtab[] = { + { "UPGRADE", SRC_LOCAL_OPER, c_a_upgrade, 0 }, + { } +}; + +TETHYS_MODULE_V1( + "core/upgrade", "Hugo Landau", "UPGRADE command", + NULL, NULL, upgrade_cmdtab); +/* vim: set noet: */ diff --git a/src/chan.c b/src/chan.c index 0327ee1..36adb43 100644 --- a/src/chan.c +++ b/src/chan.c @@ -233,7 +233,7 @@ static int cb_join(u_modes *m, int on, char *arg) return on; } -static u_chan *chan_create_real(char *name) +static u_chan *chan_create_real(const char *name) { u_chan *chan; @@ -767,6 +767,294 @@ int u_is_muted(u_chanuser *cu) return CU_MUTED; /* not 1, to mimic cu->flags & CU_MUTED */ } +static int restore_specific_chan(const char *name, mowgli_json_t *jch) +{ + int err; + int i; + u_chan *ch = NULL; + mowgli_json_t *jmasks, *jckflags, *jmems, *jmem, *jmemckflags, *jiuid; + mowgli_list_t *maska, *invites; + mowgli_string_t *jstopic, *jstopicsetter, *jsforward, *jskey, *jsmask, *jssetter, *jsiuid; + mowgli_json_t *jmask; + mowgli_node_t *n; + mowgli_patricia_iteration_state_t state; + u_listent *le; + u_ts_t time = 0; + u_user *u; + u_chanuser *cu; + const char *k; + char uid[10] = {}; + + if (strlen(name) > MAXCHANNAME) + return -1; + + ch = chan_create_real(name); + + jmasks = json_ogeto(jch, "masks"); + if (!jmasks) + return -1; + + if ((err = json_ogetu64(jch, "ts", &ch->ts)) < 0) + return err; + if ((err = json_ogetu(jch, "flags", &ch->flags)) < 0) + return err; + if ((err = json_ogeti(jch, "limit", &ch->limit)) < 0) + return err; + if ((err = json_ogetu(jch, "mode", &ch->mode)) < 0) + return err; + if ((err = json_ogetu64(jch, "topic_time", &ch->topic_time)) < 0) + return err; + jckflags = json_ogeto(jch, "ck_flags"); + if (!jckflags) { + err = -1; + return err; + } + if ((err = u_cookie_from_json(jckflags, &ch->ck_flags)) < 0) + return err; + + jstopic = json_ogets(jch, "topic"); + if (!jstopic || jstopic->pos > MAXTOPICLEN) + return err; + memcpy(ch->topic, jstopic->str, jstopic->pos); + + jstopicsetter = json_ogets(jch, "topic_setter"); + if (!jstopicsetter || jstopicsetter->pos > MAXNICKLEN) + return err; + memcpy(ch->topic_setter, jstopicsetter->str, jstopicsetter->pos); + + jsforward = json_ogets(jch, "forward"); + if (jsforward) { + ch->forward = malloc(jsforward->pos + 1); + memcpy(ch->forward, jsforward->str, jsforward->pos); + ch->forward[jsforward->pos] = '\0'; + } + + jskey = json_ogets(jch, "key"); + if (jskey) { + ch->key = malloc(jskey->pos + 1); + memcpy(ch->key, jskey->str, jskey->pos); + ch->key[jskey->pos] = '\0'; + } + + /* MASKS */ + struct masklist { + mowgli_list_t *list; + const char *type; + } masklists[] = { + {&ch->ban, "b"}, + {&ch->quiet, "q"}, + {&ch->banex, "e"}, + {&ch->invex, "I"}, + }; + + for (i=0;ihead) { + jmask = n->data; + jsmask = json_ogets (jmask, "mask"); + if (!jsmask || jsmask->pos > 255) { + err = -1; + return err; + } + + jssetter = json_ogets (jmask, "setter"); + if (!jssetter || jssetter->pos > 255) { + err = -1; + return err; + } + + if ((err = json_ogetu64(jmask, "time", &time)) < 0) + return err; + + le = malloc(sizeof(*le)); + memcpy(le->mask, jsmask->str, jsmask->pos); + le->mask[jsmask->pos] = '\0'; + memcpy(le->setter, jssetter->str, jssetter->pos); + le->setter[jssetter->pos] = '\0'; + le->time = time; + + mowgli_node_add(le, &le->n, masklists[i].list); + } + } + + jmems = json_ogeto_c(jch, "members"); + if (!jmems) { + err = -1; + return err; + } + + MOWGLI_PATRICIA_FOREACH(jmem, &state, MOWGLI_JSON_OBJECT(jmems)) { + k = mowgli_patricia_elem_get_key(state.pspare[0]); + u = u_user_by_uid(k); + if (!u) { + err = -1; + return err; + } + cu = u_chan_user_add(ch, u); + if ((err = json_ogetu(jmem, "flags", &cu->flags)) < 0) + return err; + jmemckflags = json_ogeto(jmem, "ck_flags"); + if ((err = u_cookie_from_json(jmemckflags, &cu->ck_flags)) < 0) + return err; + } + + invites = json_ogeta(jch, "invites"); + if (!invites) { + err = -1; + return err; + } + + MOWGLI_LIST_FOREACH(n, invites->head) { + jiuid = n->data; + if (!jiuid || MOWGLI_JSON_TAG(jiuid) != MOWGLI_JSON_TAG_STRING) { + err = -1; + return err; + } + + jsiuid = MOWGLI_JSON_STRING(jiuid); + if (jsiuid->pos != 9) { + err = -1; + return err; + } + + memcpy(uid, jsiuid->str, 9); + + u = u_user_by_uid(uid); + if (!u) { + err = -1; + return err; + } + + u_add_invite(ch, u); + } + + return 0; +} + +int restore_chan(void) +{ + int err; + mowgli_json_t *jchans = json_ogeto(upgrade_json, "channels"); + const char *k; + mowgli_json_t *jch; + mowgli_patricia_iteration_state_t state; + + if (!jchans) + return -1; + + u_log(LG_DEBUG, "Restoring channels..."); + MOWGLI_PATRICIA_FOREACH(jch, &state, MOWGLI_JSON_OBJECT(jchans)) { + k = mowgli_patricia_elem_get_key(state.pspare[0]); + if ((err = restore_specific_chan(k, jch)) < 0) + return err; + } + u_log(LG_DEBUG, "Done restoring channels"); + + return 0; +} + +static int dump_specific_chan(u_chan *ch, mowgli_json_t *j_chans) +{ + int i; + mowgli_node_t *n; + struct u_listent *m; + u_map_each_state st; + u_user *u; + u_chanuser *cu; + mowgli_json_t *jch, *jmask, *jmasks, *jmasktype, + *jinvites, *jinvite, + *jmems, *jmem; + + struct masklist { + mowgli_list_t *list; + const char *type; + } masklists[] = { + {&ch->ban, "b"}, + {&ch->quiet, "q"}, + {&ch->banex, "e"}, + {&ch->invex, "I"}, + }; + + jch = mowgli_json_create_object(); + json_oseto (j_chans, ch->name, jch); + + json_osets (jch, "topic", ch->topic); + json_osets (jch, "topic_setter", ch->topic_setter); + json_oseti64(jch, "topic_time", ch->topic_time); + json_oseti (jch, "mode", ch->mode); + json_oseti (jch, "flags", ch->flags); + json_osets (jch, "forward", ch->forward); + json_osets (jch, "key", ch->key); + json_oseti (jch, "limit", ch->limit); + json_oseti64(jch, "ts", ch->ts); + json_oseto (jch, "ck_flags", u_cookie_to_json(&ch->ck_flags)); + + jmasks = mowgli_json_create_object(); + json_oseto (jch, "masks", jmasks); + + /* MASKS */ + for (i=0; ihead) { + m = n->data; + + jmask = mowgli_json_create_object(); + json_append(jmasktype, jmask); + json_osets (jmask, "mask", m->mask); + json_osets (jmask, "setter", m->setter); + json_oseti64(jmask, "time", m->time); + } + } + + /* INVITES */ + jinvites = mowgli_json_create_array(); + json_oseto (jch, "invites", jinvites); + + + U_MAP_EACH(&st, ch->invites, &u, &u) { + jinvite = mowgli_json_create_string(u->uid); + json_append(jinvites, jinvite); + } + + /* MEMBERS */ + jmems = mowgli_json_create_object(); + json_oseto (jch, "members", jmems); + + U_MAP_EACH(&st, ch->members, &u, &cu) { + jmem = mowgli_json_create_object(); + json_oseto(jmems, u->uid, jmem); + json_oseti(jmem, "flags", cu->flags); + json_oseto(jmem, "ck_flags", u_cookie_to_json(&cu->ck_flags)); + } + + return 0; +} + +int dump_chan(void) +{ + int err; + u_chan *ch; + mowgli_patricia_iteration_state_t state; + + mowgli_json_t *j_chans = mowgli_json_create_object(); + json_oseto(upgrade_json, "channels", j_chans); + + MOWGLI_PATRICIA_FOREACH(ch, &state, all_chans) { + if ((err = dump_specific_chan(ch, j_chans)) < 0) + return err; + } + + return 0; +} + +/* Initialization + * -------------- + */ int init_chan(void) { int i; @@ -792,3 +1080,5 @@ int init_chan(void) return 0; } + +/* vim: set noet: */ diff --git a/src/conn.c b/src/conn.c index 9b5f1bb..e4c688e 100644 --- a/src/conn.c +++ b/src/conn.c @@ -511,3 +511,129 @@ int init_conn(void) return 0; } + +/* Serialization + * ------------- + */ +static mowgli_json_t *_pollable_to_json(mowgli_eventloop_pollable_t *poll) +{ + mowgli_json_t *jp; + + jp = mowgli_json_create_object(); + json_oseti(jp, "fd", poll->fd); + + return jp; +} + +static mowgli_eventloop_pollable_t *_pollable_from_json( + mowgli_eventloop_t *ev, + u_conn *conn, + mowgli_json_t *jp) +{ + int fd = -1; + mowgli_eventloop_pollable_t *p; + + if (json_ogeti(jp, "fd", &fd) < 0 || fd < 0) + return NULL; + + p = mowgli_pollable_create(ev, fd, conn); + + return p; +} + +mowgli_json_t *u_conn_to_json(u_conn *conn) +{ + mowgli_json_t *jc; + + jc = mowgli_json_create_object(); + json_oseti (jc, "state", conn->state); + json_oseto (jc, "poll", _pollable_to_json(conn->poll)); + json_osets (jc, "ip", conn->ip); + json_osets (jc, "host", conn->host); + json_oseto (jc, "sendq", u_sendq_to_json(&conn->sendq)); + + /* If we have dnsq then we are waiting on RDNS, we will reissue it on + * restore. + */ + json_osetb (jc, "rdns_pending", !!conn->dnsq); + + return jc; +} + +u_conn *u_conn_from_json( + mowgli_eventloop_t *ev, + u_conn_ctx *ctx, + void *priv, + mowgli_json_t *jc) +{ + int fd; + u_conn *conn; + mowgli_json_t *jpoll, *jsq; + mowgli_string_t *jsip, *jshost; + struct sockaddr_in a4; + struct sockaddr_in6 a6; + + conn = calloc(1, sizeof(*conn)); + u_sendq_init(&conn->sendq); + + if (json_ogetu(jc, "state", &conn->state) < 0) + goto error; + + jpoll = json_ogeto(jc, "poll"); + if (!jpoll) + goto error; + jsq = json_ogeto(jc, "sendq"); + if (!jsq) + goto error; + + jsip = json_ogets(jc, "ip"); + if (!jsip || jsip->pos > INET6_ADDRSTRLEN) + goto error; + memcpy(conn->ip, jsip->str, jsip->pos); + conn->ip[jsip->pos] = '\0'; + + jshost = json_ogets(jc, "host"); + if (!jshost || jshost->pos > U_CONN_HOSTSIZE) + goto error; + memcpy(conn->host, jshost->str, jshost->pos); + conn->host[jshost->pos] = '\0'; + + conn->poll = _pollable_from_json(ev, conn, jpoll); + if (!conn->poll) + goto error; + + if (u_sendq_from_json(jsq, &conn->sendq) < 0) + goto error; + + conn->ctx = ctx; + conn->priv = priv; + + if (conn->ctx->attach) + conn->ctx->attach(conn); + + /* If RDNS was pending, reissue the query. */ + if (json_ogetb(jc, "rdns_pending")) { + /* Parse the IP... */ + if (inet_pton(AF_INET6, conn->ip, &a6)) + rdns_start(conn, (struct sockaddr*)&a6, sizeof(a6)); + else if (inet_pton(AF_INET, conn->ip, &a4)) + rdns_start(conn, (struct sockaddr*)&a4, sizeof(a4)); + } + + sync_on_update(conn); + + return conn; + +error: + u_sendq_clear(&conn->sendq); + if (conn->poll) { + fd = conn->poll->fd; + mowgli_pollable_destroy(ev, conn->poll); + close(fd); + /* Closing fds means this function fails non-idempotently. */ + } + free(conn); + return NULL; +} + +/* vim: set noet: */ diff --git a/src/cookie.c b/src/cookie.c index e6af3b1..9b5869c 100644 --- a/src/cookie.c +++ b/src/cookie.c @@ -40,3 +40,26 @@ int u_cookie_cmp(u_cookie *a, u_cookie *b) return norm(a->low - b->low); return norm(a->high - b->high); } + +mowgli_json_t *u_cookie_to_json(u_cookie *a) +{ + mowgli_json_t *jc; + + jc = mowgli_json_create_object(); + json_oseti64(jc, "high", a->high); + json_oseti64(jc, "low", a->low); + + return jc; +} + +int u_cookie_from_json(mowgli_json_t *jc, u_cookie *c) +{ + int err; + + if ((err = json_ogetu64(jc, "high", &c->high)) < 0) + return err; + if ((err = json_ogetu64(jc, "low", &c->low)) < 0) + return err; + + return 0; +} diff --git a/src/link.c b/src/link.c index c96b6f4..042981f 100644 --- a/src/link.c +++ b/src/link.c @@ -220,6 +220,12 @@ static void dispatch_lines(u_link *link) /* s now points to the current line, and buf and buflen describe the rest of the buffer */ + /* If executing this command causes an upgrade, u_cmd_invoke will not + * return. Indicate the length of the current message to the dump function + * so that it won't be serialized and re-execute after upgrade. + */ + link->ibufskip = (p - link->ibuf); + /* dispatch the line */ u_log(LG_DEBUG, "[%G] -> %s", link, s); if (u_msg_parse(&msg, (char*)s) < 0) @@ -230,6 +236,11 @@ static void dispatch_lines(u_link *link) /* move remaining buffer contents to the start of the in buffer */ memmove(link->ibuf, link->ibuf + link->ibuflen - buflen, buflen); link->ibuflen = buflen; + link->ibufskip = 0; +} + +void u_link_flush_input(u_link *link) { + dispatch_lines(link); } /* user API */ @@ -373,6 +384,40 @@ static mowgli_patricia_t *u_conf_listen_handlers = NULL; static void accept_ready(mowgli_eventloop_t *ev, mowgli_eventloop_io_t *io, mowgli_eventloop_io_dir_t dir, void *priv); +u_link_origin *u_link_origin_create_from_fd(mowgli_eventloop_t *ev, int fd) +{ + const char *operation; + u_link_origin *origin = NULL; + + /* Set the fd to be close-on-exec. All listening sockets will be closed when + * we upgrade. This may cause some slight disturbance for users currently + * connecting, but this is acceptable. + */ + operation = "set close-on-exec"; + if (set_cloexec(fd) < 0) + goto error; + + u_log(LG_DEBUG, "u_link_origin_create_from_fd: %d", fd); + + origin = malloc(sizeof(*origin)); + + operation = "create pollable"; + if (!(origin->poll = mowgli_pollable_create(ev, fd, origin))) { + errno = -EINVAL; /* XXX */ + goto error; + } + + mowgli_node_add(origin, &origin->n, &all_origins); + mowgli_pollable_setselect(ev, origin->poll, MOWGLI_EVENTLOOP_IO_READ, + accept_ready); + + return origin; +error: + u_perror(operation); + free(origin); + return NULL; +} + u_link_origin *u_link_origin_create(mowgli_eventloop_t *ev, short port) { const char *operation; @@ -402,24 +447,13 @@ u_link_origin *u_link_origin_create(mowgli_eventloop_t *ev, short port) if (listen(fd, 5) < 0) goto error; - origin = malloc(sizeof(*origin)); - - operation = "create pollable"; - if (!(origin->poll = mowgli_pollable_create(ev, fd, origin))) { - errno = -EINVAL; /* XXX */ + if (!(origin = u_link_origin_create_from_fd(ev, fd))) goto error; - } - - mowgli_node_add(origin, &origin->n, &all_origins); - mowgli_pollable_setselect(ev, origin->poll, MOWGLI_EVENTLOOP_IO_READ, - accept_ready); return origin; error: u_perror(operation); - if (origin) - free(origin); if (fd >= 0) close(fd); return NULL; @@ -519,3 +553,127 @@ int init_link(void) return 0; } + +/* Serialization + * ------------- + */ +mowgli_json_t *u_link_to_json(u_link *link) +{ + mowgli_json_t *jl; + + if (!link) + return NULL; + + jl = mowgli_json_create_object(); + json_oseti (jl, "flags", link->flags); + json_oseti (jl, "type", link->type); + json_osets (jl, "pass", link->pass); + json_oseti (jl, "sendq", link->sendq); + json_oseto (jl, "ck_sendto", u_cookie_to_json(&link->ck_sendto)); + json_oseto (jl, "conn", u_conn_to_json(link->conn)); + json_osetb64(jl, "ibuf", link->ibuf + link->ibufskip, link->ibuflen - link->ibufskip); + + switch (link->type) { + case LINK_USER: + /* we can figure this out when we restore */ + break; + + case LINK_SERVER: + json_osets (jl, "server_link_block_name", link->conf.link->name); + break; + + default: + u_log(LG_SEVERE, "unexpected link type %d", link->type); + abort(); + } + + return jl; +} + +u_link *u_link_from_json(mowgli_json_t *jl) +{ + u_link *link; + mowgli_json_t *jcookie, *jconn; + mowgli_string_t *jpass, *jslinkname; + ssize_t sz; + + link = link_create(); + + if (json_ogetu(jl, "flags", &link->flags) < 0) + goto error; + if (json_ogetu(jl, "type", &link->type) < 0) + goto error; + if (json_ogeti(jl, "sendq", &link->sendq) < 0) + goto error; + + if ((sz = json_ogetb64(jl, "ibuf", link->ibuf, IBUFSIZE)) < 0) + goto error; + + link->ibuflen = sz; + link->ibuf[link->ibuflen] = '\0'; + + jpass = json_ogets(jl, "pass"); + if (jpass) { + link->pass = malloc(jpass->pos+1); + memcpy(link->pass, jpass->str, jpass->pos); + link->pass[jpass->pos] = '\0'; + } + + jcookie = json_ogeto(jl, "ck_sendto"); + if (!jcookie) + goto error; + + if (u_cookie_from_json(jcookie, &link->ck_sendto) < 0) + goto error; + + jconn = json_ogeto(jl, "conn"); + if (!jconn) + goto error; + + link->conn = u_conn_from_json(base_ev, &u_link_conn_ctx, link, jconn); + if (!link->conn) + goto error; + + link->conn->priv = link; + + /* This must run after the config has been loaded. */ + switch (link->type) { + case LINK_USER: + /* If the user is post-registration, re-find his auth block. */ + if (link->flags & U_LINK_REGISTERED) { + /* XXX: Should this be in user.c? */ + link->conf.auth = u_find_auth(link); + if (!link->conf.auth) + goto error; + } + + break; + + case LINK_SERVER: + jslinkname = json_ogets(jl, "server_link_block_name"); + if (!jslinkname) + goto error; + + /* mowgli_string is NULL-terminated. We needn't care about NULLs. */ + link->conf.link = u_find_link(jslinkname->str); + if (!link->conf.link) + goto error; + + break; + + default: + u_log(LG_SEVERE, "unexpected link type %d", link->type); + abort(); + } + + return link; + +error: + if (link) { + free(link->pass); + free(link); + } + return NULL; +} + +/* vim: set noet: */ diff --git a/src/main.c b/src/main.c index 3f606fc..ceb8aed 100644 --- a/src/main.c +++ b/src/main.c @@ -20,6 +20,8 @@ char startedstr[256]; short opt_port = -1; +char *main_argv0; + int usage(char *argv0, int code) { printf("Usage: %s [OPTIONS]\n", argv0); @@ -44,16 +46,16 @@ int init(void) base_ev = mowgli_eventloop_create(); base_dns = mowgli_dns_create(base_ev, MOWGLI_DNS_TYPE_ASYNC); + INIT(init_upgrade); INIT(init_util); INIT(init_module); INIT(init_hook); INIT(init_conf); INIT(init_conn); - INIT(init_upgrade); INIT(init_auth); + INIT(init_server); INIT(init_user); INIT(init_cmd); - INIT(init_server); INIT(init_chan); INIT(init_sendto); INIT(init_link); @@ -70,6 +72,17 @@ int init(void) return -1; } + /* Upgrade */ + if (upgrade_json) { + INIT(restore_server); + INIT(restore_user); + INIT(restore_chan); + finish_upgrade(); + u_server_flush_inputs(); + u_user_flush_inputs(); + } + /* Any upgrade is now complete. */ + return 0; } @@ -78,13 +91,15 @@ int main(int argc, char **argv) { int c; + main_argv0 = argv[0]; + gettimeofday(&NOW, NULL); started = NOW.tv_sec; strftime(startedstr, 256, "%a %b %d at %T UTC", gmtime((time_t*) &started)); u_log(LG_INFO, "%s starting...", PACKAGE_FULLNAME); - while ((c = getopt(argc, argv, "vhp:")) != -1) { + while ((c = getopt(argc, argv, "vhp:U:")) != -1) { switch(c) { case 'v': if (u_log_level < LG_FINE) @@ -98,6 +113,9 @@ int main(int argc, char **argv) " config file to specify listeners"); opt_port = atoi(optarg); break; + case 'U': + opt_upgrade = optarg; + break; case '?': usage(argv[0], 1); break; @@ -106,6 +124,7 @@ int main(int argc, char **argv) if (init() < 0) { u_log(LG_ERROR, "Initialization failed"); + abort_upgrade(); return 1; } diff --git a/src/numeric.tab b/src/numeric.tab index 4928f95..d79e164 100644 --- a/src/numeric.tab +++ b/src/numeric.tab @@ -116,6 +116,8 @@ RPL_ENDOFMOTD 376 ":End of /MOTD command" RPL_YOUREOPER 381 ":MAIN SCREEN TURN ON" RPL_REHASHING 382 ":Rehashing" +RPL_UPGRADEFAILED 383 ":Upgrade failed." +RPL_UPGRADESTARTING 384 ":Now upgrading..." RPL_TIME 391 RPL_USERSSTART 392 diff --git a/src/ratelimit.c b/src/ratelimit.c index 4b28eaa..7f41752 100644 --- a/src/ratelimit.c +++ b/src/ratelimit.c @@ -61,3 +61,33 @@ void u_ratelimit_who_deduct(u_user *user) if (user->limit.whotokens > 0) user->limit.whotokens--; } + +mowgli_json_t *u_ratelimit_to_json(u_ratelimit_t *limit) +{ + mowgli_json_t *o = mowgli_json_create_object(); + json_osetu(o, "tokens", limit->tokens); + json_osetu(o, "whotokens", limit->tokens); + json_oseti64(o, "last", limit->last); + + return o; +} + +int u_ratelimit_from_json(mowgli_json_t *jrl, u_ratelimit_t *limit) +{ + int err; + if (MOWGLI_JSON_TAG(jrl) != MOWGLI_JSON_TAG_OBJECT) + return -1; + + if ((err = json_ogetu(jrl, "tokens", &limit->tokens)) < 0) + return err; + + if ((err = json_ogetu(jrl, "whotokens", &limit->tokens)) < 0) + return err; + + if ((err = json_ogeti64(jrl, "last", &limit->last)) < 0) + return err; + + return 0; +} + +/* vim: set noet: */ diff --git a/src/sendq.c b/src/sendq.c index a593419..d4354de 100644 --- a/src/sendq.c +++ b/src/sendq.c @@ -10,6 +10,7 @@ /* ------ */ #define SENDQ_CHUNK_SIZE 4000 +#define SENDQ_B64_CHUNK_SIZE 5328 /* corresponds to just under 4000 bytes */ #define CHUNK_IN_USE 0x0001 @@ -191,3 +192,75 @@ int u_sendq_write(u_sendq *q, int fd) return 0; } + +/* Serialization + * ------------- + */ +mowgli_json_t *u_sendq_to_json(u_sendq *sq) +{ + mowgli_json_t *jsq, *jbuf; + u_sendq_chunk *c; + char *ocur; + char *sqbuf; + size_t sqbuf_len; + + if (!sq) + return NULL; + + /* Might want to optimize this in the future. */ + sqbuf_len = base64_inflate_size(sq->size); + sqbuf = malloc(sqbuf_len); + + ocur = sqbuf; + for (c=sq->head; c; c=c->next) { + /* we can assume the chunk is in use */ + ocur += base64_encode(c->data + c->start, c->end - c->start, + sqbuf, ocur); + /* invariant: ocur <= sqbuf + sqbuf_len */ + } + + jsq = mowgli_json_create_object(); + jbuf = mowgli_json_create_string_n(sqbuf, ocur - sqbuf); + json_oseto(jsq, "buf", jbuf); + + free(sqbuf); + return jsq; +} + +int u_sendq_from_json(mowgli_json_t *jsq, u_sendq *sq) +{ + mowgli_string_t *jsbuf; + size_t len; + char *cur, *end, *echunk; + void *sqbuf; + + jsbuf = json_ogets(jsq, "buf"); + if (!jsbuf || jsbuf->pos % 4) + return -1; + + cur = jsbuf->str; + end = jsbuf->str + jsbuf->pos; + + /* base64 data length must be divisible by 4. */ + if (jsbuf->pos % 4) + return -1; + + /* We choose to decode in blocks of 5328 characters, as this corresponds to + * just under 4000 bytes and is divisible by 4. + */ + while (cur < end) { + echunk = cur + SENDQ_B64_CHUNK_SIZE; + if (echunk > end) + echunk = end; + + sqbuf = u_sendq_get_buffer(sq, SENDQ_CHUNK_SIZE); + len = base64_decode(cur, echunk-cur, sqbuf); + u_sendq_end_buffer(sq, len); + + cur = echunk; + } + + return 0; +} + +/* vim: set noet: */ diff --git a/src/server.c b/src/server.c index 8b3bf7d..62d3746 100644 --- a/src/server.c +++ b/src/server.c @@ -78,12 +78,12 @@ static void admin_conf(mowgli_config_file_t *cf, mowgli_config_file_entry_t *ce) } } -u_server *u_server_by_sid(char *sid) +u_server *u_server_by_sid(const char *sid) { return mowgli_patricia_retrieve(servers_by_sid, sid); } -u_server *u_server_by_name(char *name) +u_server *u_server_by_name(const char *name) { return mowgli_patricia_retrieve(servers_by_name, name); } @@ -423,11 +423,197 @@ void u_server_eob(u_server *sv) sv->flags &= ~SERVER_IS_BURSTING; } +void u_server_flush_inputs(void) +{ + mowgli_patricia_iteration_state_t state; + u_server *s; + + MOWGLI_PATRICIA_FOREACH(s, &state, servers_by_sid) { + if (s->link) + u_link_flush_input(s->link); + } +} + +/* Serialization + * ------------- + */ +static int dump_specific_server(u_server *s, mowgli_json_t *j_servers) +{ + mowgli_json_t *js; + + if (!s->sid[0]) + /* A server entry is created for juped servers, but with no sid. + * Just ignore it, it'll be recreated from the config file. + */ + return 0; + + js = mowgli_json_create_object(); + json_oseto (j_servers, s->sid, js); + + json_osets (js, "name", s->name); + json_osets (js, "desc", s->desc); + json_oseti (js, "capab", s->capab); + json_oseti (js, "hops", s->hops); + json_osets (js, "parent", (s->parent) ? s->parent->sid : NULL); + json_oseto (js, "link", u_link_to_json(s->link)); + json_osetu (js, "nlinks", s->nlinks); + + return 0; +} + +int dump_server(void) +{ + int err; + mowgli_patricia_iteration_state_t state; + u_server *s; + + mowgli_json_t *j_servers = mowgli_json_create_object(); + json_oseto(upgrade_json, "servers", j_servers); + + MOWGLI_PATRICIA_FOREACH(s, &state, servers_by_sid) { + if ((err = dump_specific_server(s, j_servers)) < 0) + return err; + } + + return 0; +} + +static int restore_specific_server(const char *sid, mowgli_json_t *js) +{ + static bool _restored_me = false; + u_server *s = NULL; + u_server *sparent = NULL; + u_link *link = NULL; + char psid[4] = {}; + mowgli_string_t *jsparent, *jsname, *jsdesc; + mowgli_json_t *jlink; + + if (strlen(sid) != 3) + return -1; + + if (!memcmp(sid, me.sid, 3)) + s = &me; + + /* We already have this server. But since we've already read the + * configuration file, 'me' will be fully loaded and in the lookup tables. + * So we only read a few fields when s == &me. + */ + if (u_server_by_sid(sid) && (s != &me || _restored_me)) + return 0; + + /* Parent ----------------------------- */ + jsparent = json_ogets(js, "parent"); + if (jsparent) { + if (s == &me || jsparent->pos != 3) + return -1; + + memcpy(psid, jsparent->str, 3); + sparent = u_server_by_sid(psid); + + /* Come back to this later if our parent isn't added yet. */ + if (!sparent) + return 0; + } + + u_log(LG_DEBUG, "Restoring server [%s]", sid); + jlink = json_ogeto(js, "link"); + + if (!s) + s = calloc(1, sizeof(*s)); + + if (json_ogetu(js, "hops", &s->hops) < 0) + return -1; + + if (json_ogetu(js, "capab", &s->capab) < 0) + return -1; + + if (json_ogetu(js, "nlinks", &s->nlinks) < 0) + return -1; + + if (s == &me) { + /* name and description must match what is in the + * configuration file for 'me' + */ + if (jlink) + return -1; + + _restored_me = true; + } else { + if (!jlink) + return -1; + + link = u_link_from_json(jlink); + if (!link) + return -1; + + memcpy(s->sid, sid, 3); + s->link = link; + s->link->priv = s; + s->parent = sparent; + + jsname = json_ogets(js, "name"); + if (!jsname || jsname->pos > MAXSERVNAME) + return -1; + memcpy(s->name, jsname->str, jsname->pos); + + jsdesc = json_ogets(js, "desc"); + if (!jsdesc || jsname->pos > MAXSERVDESC) + return -1; + memcpy(s->desc, jsdesc->str, jsdesc->pos); + + mowgli_patricia_add(servers_by_sid, s->sid, s); + mowgli_patricia_add(servers_by_name, s->name, s); + } + + return 1; +} + +int restore_server(void) +{ + int err; + mowgli_json_t *jss, *js; + mowgli_patricia_iteration_state_t state; + int num_added; + const char *k; + + u_log(LG_DEBUG, "Restoring servers..."); + + /* Add servers */ + jss = json_ogeto_c(upgrade_json, "servers"); + if (!jss) + return -1; + + do { + num_added = 0; + MOWGLI_PATRICIA_FOREACH(js, &state, MOWGLI_JSON_OBJECT(jss)) { + k = mowgli_patricia_elem_get_key(state.pspare[0]); /* XXX */ + err = restore_specific_server(k, js); + if (err < 0) + return err; + if (err > 0) + ++num_added; + } + } while (num_added); + + u_log(LG_DEBUG, "Done restoring servers"); + return 0; +} + +/* Unit Initialization + * ------------------- + */ int init_server(void) { servers_by_sid = mowgli_patricia_create(ascii_canonize); servers_by_name = mowgli_patricia_create(ascii_canonize); + mowgli_list_init(&my_motd); + + u_strlcpy(my_net_name, "TethysIRC", MAXNETNAME+1); + + u_conf_add_handler("me", server_conf, NULL); + u_conf_add_handler("admin", admin_conf, NULL); + /* default settings! */ me.link = NULL; strcpy(me.sid, "22U"); @@ -446,12 +632,7 @@ int init_server(void) mowgli_patricia_add(servers_by_name, me.name, &me); mowgli_patricia_add(servers_by_sid, me.sid, &me); - mowgli_list_init(&my_motd); - - u_strlcpy(my_net_name, "TethysIRC", MAXNETNAME+1); - - u_conf_add_handler("me", server_conf, NULL); - u_conf_add_handler("admin", admin_conf, NULL); - return 1; } + +/* vim: set noet: */ diff --git a/src/upgrade.c b/src/upgrade.c index 968ea72..f947e6d 100644 --- a/src/upgrade.c +++ b/src/upgrade.c @@ -4,96 +4,221 @@ This file is protected under the terms contained in the COPYING file in the project root */ -#define UDB_LINE_SIZE 4096 - #include "ircd.h" -struct u_udb { - int reading; +const char *opt_upgrade; +mowgli_json_t *upgrade_json; - FILE *f; +static int +_form_phoenix_args(const char *upgrade_fn, const char ***argv) +{ + static const char *_args[8]; + static char _verbosity[16] = "-"; + static char _port[6]; - char line[UDB_LINE_SIZE]; - char buf[UDB_LINE_SIZE]; - char *p; - ulong sz; -}; + int i = 0; + int level = LG_INFO; + char *vflag = _verbosity + 1; -mowgli_list_t *u_udb_save_hooks; -mowgli_patricia_t *u_udb_load_hooks; + _args[i++] = main_argv0; -void u_udb_row_start(u_udb *db, char *name) -{ - if (db->reading) { - u_log(LG_ERROR, "Tried to start row while reading database!"); - return; + if (level < u_log_level) { + while (level++ < u_log_level) + *vflag++ = 'v'; + *vflag++ = 0; + _args[i++] = _verbosity; } - db->sz = snf(FMT_LOG, db->line, UDB_LINE_SIZE, "%s", name); -} + if (opt_port >= 0) { + sprintf(_port, "%d", opt_port); + _args[i++] = "-p"; + _args[i++] = _port; + } -void u_udb_row_end(u_udb *db) -{ - fprintf(db->f, "%s\n", db->line); + if (upgrade_fn) { + _args[i++] = "-U"; + _args[i++] = upgrade_fn; + } + + _args[i++] = NULL; + + *argv = _args; + return 0; } -void u_udb_put_s(u_udb *db, char *s) +/* begin_upgrade + * ------------- + * Called to begin an upgrade. Re-execs. Does not return on success. + */ + +static void +_json_append(mowgli_json_output_t *out, const char *str, size_t len) { - char *p = db->buf; - - while (*s) { - switch (*s) { - case ' ': - case '\\': - *p++ = '\\'; - default: - *p++ = *s++; - } + FILE *f = out->priv; + if (fwrite(str, len, 1, f) != 1) { + /* XXX */ + abort(); } +} - db->sz += snf(FMT_LOG, db->line + db->sz, - UDB_LINE_SIZE - db->sz, " %s", s); +static void +_json_append_char(mowgli_json_output_t *out, const char c) +{ + _json_append(out, &c, sizeof(c)); } -void u_udb_put_i(u_udb *db, long n) +int +begin_upgrade(void) { - char buf[512]; + int err; + const char **argv; + FILE *f; + mowgli_json_output_t json_out = {}; + u_hook *h_dump; + + /* Make sure the file doesn't currently exist. */ + if (unlink(UPGRADE_FILENAME) < 0 && errno != ENOENT) + return -1; + + f = fopen(UPGRADE_FILENAME, "wb"); + if (!f) + return -1; + + /* Open database */ + upgrade_json = mowgli_json_create_object(); + + /* Call each unit and give it an opportunity to dump information */ +#define DUMP(fn) if ((err = (fn)()) < 0) goto error + DUMP(dump_user); + DUMP(dump_server); + DUMP(dump_chan); + + h_dump = u_hook_get(HOOK_UPGRADE_DUMP); + if (u_hook_first(h_dump, NULL)) { + /* return non-NULL to abort */ + return -1; + } + + json_out.priv = f; + json_out.append = _json_append; + json_out.append_char = _json_append_char; - snf(FMT_LOG, buf, 512, "%d", n); - u_udb_put_s(db, buf); + mowgli_json_serialize(upgrade_json, &json_out, 1 /* DEBUG PRETTY */); + + mowgli_json_decref(upgrade_json); + upgrade_json = NULL; + + fclose(f); + + /* Launch successor */ + if ((err = _form_phoenix_args(UPGRADE_FILENAME, &argv)) < 0) + goto error; + + return execvp(argv[0], (char**)argv); + +error: + if (upgrade_json) { + mowgli_json_decref(upgrade_json); + upgrade_json = NULL; + } + return err; } -char *u_udb_get_s(u_udb *db) +/* finish_upgrade + * -------------- + * Called when all modules are initialized and have had a chance to make use of + * upgrade data. + */ +int +finish_upgrade(void) { - char *s = db->buf; + if (upgrade_json) { + u_hook *h_restore = u_hook_get(HOOK_UPGRADE_RESTORE); + if (u_hook_first(h_restore, NULL)) { + /* return non-NULL to abort */ + return -1; + } - if (!db->reading) { - u_log(LG_ERROR, "Tried to read word while writing database!"); - return NULL; + mowgli_json_decref(upgrade_json); + upgrade_json = NULL; + //unlink(opt_upgrade); } + opt_upgrade = NULL; + return 0; +} - while (*db->p != ' ' && *db->p) { - if (*db->p == '\\') - db->p++; - *s++ = *db->p++; - } - *s = '\0'; +/* abort_upgrade + * ------------- + * In the event that restore fails, we re-execute ourself in non-upgrade mode. + * One could design the restore system to be transactional, but that creates a + * much greater degree of complexity. The objective of this system is to be + * simple and maintainable. Strange though double-exec may be, it's a simple + * and effective failsafe. Since the upgrade dump is written and read by (more + * or less) the same code, failures really shouldn't happen. But in the + * unlikely event that that does happen, we can at least continue operation + * and accept new connections rather than simply crash. + * + * This means that the restore_ functions leak memory and result in undefined + * state on failure. The program must exit when this occurs. This is comparable + * to the init_ functions, so it matches the style of Tethys. + * + * Proper error handling is retained for the '_from_json' functions at this + * time, since there is no clear motive to remove it. + */ +static void +_closefrom(int fd) +{ + int maxfd; + + /* This is POSIX and should be portable. */ + maxfd = (int)sysconf(_SC_OPEN_MAX); + if (maxfd < 0) + maxfd = 256; /* arbitrary */ - return db->buf; + for (; fd0;) { id_digits[i] ++; if (id_digits[i] < id_modulus) @@ -29,6 +29,23 @@ char *id_next(void) return id_buf; } +static int set_id_next_from_str(const char *id) +{ + char d[6] = {}; + char *x; + int i; + + for (i=0;i<6;++i) { + x = strchr(id_map, id[i]); + if (!x) + return -1; + d[i] = x - id_map; + } + + memcpy(id_digits, d, 6); + return 0; +} + static ulong umode_get_flag_bits(u_modes *m) { return ((u_user*) m->target)->mode; @@ -62,7 +79,7 @@ u_mode_ctx umodes = { uint umode_default = 0; -static u_user *create_user(char *uid, u_link *link, u_server *sv) +static u_user *create_user(const char *uid, u_link *link, u_server *sv) { u_user *u; @@ -179,23 +196,23 @@ void u_user_try_register(u_user *u) u_user_welcome(u); } -u_user *u_user_by_nick_raw(char *nick) +u_user *u_user_by_nick_raw(const char *nick) { return mowgli_patricia_retrieve(users_by_nick, nick); } -u_user *u_user_by_nick(char *nick) +u_user *u_user_by_nick(const char *nick) { u_user *u = u_user_by_nick_raw(nick); return u && IS_REGISTERED(u) ? u : NULL; } -u_user *u_user_by_uid_raw(char *uid) +u_user *u_user_by_uid_raw(const char *uid) { return mowgli_patricia_retrieve(users_by_uid, uid); } -u_user *u_user_by_uid(char *nick) +u_user *u_user_by_uid(const char *nick) { u_user *u = u_user_by_uid_raw(nick); return u && IS_REGISTERED(u) ? u : NULL; @@ -400,8 +417,8 @@ int u_user_in_list(u_user *u, mowgli_list_t *list) void u_user_make_euid(u_user *u, char *buf) { /* still ridiculous... nick nickts host uid acct - hops modes ip rlhost gecos - ident */ + hops modes ip rlhost gecos + ident */ snf(FMT_SERVER, buf, 512, ":%S EUID %s %d %u %s %s %s %s %s %s %s :%s", u->sv, u->nick, u->sv->hops + 1, u->nickts, u_user_modes(u), @@ -409,6 +426,234 @@ void u_user_make_euid(u_user *u, char *buf) IS_LOGGED_IN(u) ? u->acct : "*", u->gecos); } +void u_user_flush_inputs(void) +{ + mowgli_patricia_iteration_state_t state; + u_user *u; + + MOWGLI_PATRICIA_FOREACH(u, &state, users_by_uid) { + if (u->link) + u_link_flush_input(u->link); + } +} + +/* Serialization + * ------------- + */ +static int dump_specific_user(u_user *u, mowgli_json_t *j_users) +{ + mowgli_json_t *ju; + + if (!u->sv || !u->sv->sid[0]) { + u_log(LG_WARN, "User on server without SID, ignoring"); + return 0; + } + + ju = mowgli_json_create_object(); + json_oseto (j_users, u->uid, ju); + + json_oseti (ju, "mode", u->mode); + json_oseti (ju, "flags", u->flags); + json_osets (ju, "nick", u->nick); + json_osets (ju, "acct", u->acct); + json_oseti64(ju, "nickts", u->nickts); + json_osets (ju, "ident", u->ident); + json_osets (ju, "ip", u->ip); + json_osets (ju, "realhost", u->realhost); + json_osets (ju, "host", u->host); + json_osets (ju, "gecos", u->gecos); + json_osets (ju, "away", u->away); + json_oseto (ju, "limit", u_ratelimit_to_json(&u->limit)); + if (u->sv == &me) { + /* Local user. */ + json_oseto (ju, "link", u_link_to_json(u->link)); + } else { + /* Remote user; the link is serialized with the server. + * Reference it by SID. We can't infer this from the UID because our link + * to the user could be via an intermediate server. This is essentially the + * "next hop". + */ + json_osets (ju, "link_via", u->sv->sid); + } + + return 0; +} + +int dump_user(void) +{ + int err; + u_user *u; + mowgli_patricia_iteration_state_t state; + + id_next(); + json_oseto(upgrade_json, "next_uid", + mowgli_json_create_string_n(id_buf, strlen(id_buf))); + + /* Dump users */ + mowgli_json_t *j_users = mowgli_json_create_object(); + json_oseto(upgrade_json, "users", j_users); + + MOWGLI_PATRICIA_FOREACH(u, &state, users_by_uid) { + if ((err = dump_specific_user(u, j_users)) < 0) + return err; + } + + return 0; +} + +static int restore_specific_user(const char *uid, mowgli_json_t *ju) +{ + int err; + u_user *u = NULL; + u_link *link; + mowgli_json_t *jl, *jlimit; + mowgli_string_t + *jslvia, + *jsnick, *jsacct, *jsident, + *jsip, *jsrealhost, *jshost, *jsgecos, *jsaway; + u_server *sv, *sv_via; + char sid[4]; + + /* Get server for UID ------------------------- */ + if (strlen(uid) != 9) + return -1; + + memcpy(sid, uid, 3); + sid[3] = '\0'; + + sv = u_server_by_sid(sid); + if (!sv) + return -1; + + /* Create user -------------------------------- */ + u = create_user(uid, NULL, sv); + + /* Fill in various fields --------------------- */ + if ((err = json_ogetu(ju, "mode", &u->mode)) < 0) + return err; + + if ((err = json_ogetu(ju, "flags", &u->flags)) < 0) + return err; + + if ((err = json_ogetu64(ju, "nickts", &u->nickts)) < 0) + return err; + + jsnick = json_ogets(ju, "nick"); + if (!jsnick || jsnick->pos > MAXNICKLEN) + return -1; + memcpy(u->nick, jsnick->str, jsnick->pos); + + jsacct = json_ogets(ju, "acct"); + if (!jsacct || jsnick->pos > MAXACCOUNT) + return -1; + memcpy(u->acct, jsacct->str, jsacct->pos); + + jsident = json_ogets(ju, "ident"); + if (!jsident || jsnick->pos > MAXIDENT) + return -1; + memcpy(u->ident, jsident->str, jsident->pos); + + jsip = json_ogets(ju, "ip"); + if (!jsip || jsip->pos > INET_ADDRSTRLEN) + return -1; + memcpy(u->ip, jsip->str, jsip->pos); + + jsrealhost = json_ogets(ju, "realhost"); + if (!jsrealhost || jsrealhost->pos > MAXHOST) + return -1; + memcpy(u->realhost, jsrealhost->str, jsrealhost->pos); + + jshost = json_ogets(ju, "host"); + if (!jshost || jshost->pos > MAXHOST) + return -1; + memcpy(u->host, jshost->str, jshost->pos); + + jsgecos = json_ogets(ju, "gecos"); + if (!jsgecos || jsgecos->pos > MAXGECOS) + return -1; + memcpy(u->gecos, jsgecos->str, jsgecos->pos); + + jsaway = json_ogets(ju, "away"); + if (!jsaway || jsaway->pos > MAXAWAY) + return -1; + memcpy(u->away, jsaway->str, jsaway->pos); + + jlimit = json_ogeto(ju, "limit"); + if (!jlimit) + return -1; + + if ((err = u_ratelimit_from_json(jlimit, &u->limit)) < 0) + return err; + + /* Create link -------------------------------- */ + err = -1; + jl = json_ogeto(ju, "link"); + jslvia = json_ogets(ju, "link_via"); + if (!!jl == !!jslvia) /* neither both nor neither */ + return -1; + + if (jl) { + /* Local user */ + link = u_link_from_json(jl); + if (!link) + return -1; + + /* Can't fail now */ + u->link = link; + u->link->priv = u; + } else { + /* Remote user */ + sv_via = u_server_by_sid(jslvia->str); + if (!sv_via) + return -1; + + u->link = sv_via->link; + } + + mowgli_patricia_add(users_by_nick, u->nick, u); + + return 0; +} + +int restore_user(void) +{ + int err; + mowgli_json_t *ju, *jusers; + mowgli_patricia_iteration_state_t state; + mowgli_string_t *jsnextuid; + const char *k; + + u_log(LG_DEBUG, "Restoring users..."); + + /* Restore UID counter */ + jsnextuid = json_ogets(upgrade_json, "next_uid"); + if (!jsnextuid || jsnextuid->pos != 6) { + u_log(LG_DEBUG, "no next UID? %u", (jsnextuid ? jsnextuid->pos : 9999)); + return -1; + } + + if ((err = set_id_next_from_str(jsnextuid->str)) < 0) + return -1; + + /* Restore users */ + jusers = json_ogeto_c(upgrade_json, "users"); + if (!jusers) + return -1; + + MOWGLI_PATRICIA_FOREACH(ju, &state, MOWGLI_JSON_OBJECT(jusers)) { + k = mowgli_patricia_elem_get_key(state.pspare[0]); /* XXX */ + if ((err = restore_specific_user(k, ju)) < 0) + return err; + } + + /* Done */ + u_log(LG_DEBUG, "Done restoring users"); + return 0; +} + +/* Initialization + * -------------- + */ int init_user(void) { users_by_nick = mowgli_patricia_create(rfc1459_canonize); @@ -419,3 +664,5 @@ int init_user(void) return 0; } + +/* vim: set noet: */ diff --git a/src/util.c b/src/util.c index 8dfa4b5..3869bf1 100644 --- a/src/util.c +++ b/src/util.c @@ -407,6 +407,198 @@ int is_valid_chan(char *s) return 1; } +int set_cloexec(int fd) +{ +#ifdef FD_CLOEXEC + int flags; + + if ((flags = fcntl(fd, F_GETFD)) < 0) + return -1; + + if (fcntl(fd, F_SETFD, flags|FD_CLOEXEC) < 0) + return -1; +#endif + + return 0; +} + +/* Base64 Encoder + * -------------- + */ +static const char _table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +size_t +base64_encode( + const void *in_buf, size_t in_buf_len, + char *out_buf, char *out_cur) +{ + size_t i, j, jsub = 0; + uint32_t triple = 0; + uint8_t nb = 0; + const char *ib = in_buf; + uint8_t joinbuf[3] = {}; + + if (out_cur >= out_buf + 4 && out_cur[-1] == '=') { + /* This is a little odd so I'll explain. Each sendq is split into chunks. + * We have to call base64_encode separately for each chunk. In the worst + * case scenario, each chunk results in two padding characters (==). This + * throws off the worst-case size estimate of base64_inflate_size, which + * assumes a single call and only two padding characters. We want to be + * able to estimate the size of the base64 encoded data so we can allocate + * the entire memory needed up front. We need it all in contiguous memory + * so that it can form a mowgli_string_t and thus form part of the JSON + * tree. + * + * The obvious choice is to make the base64 encoder resumable. For ordinary + * operation, pass out_buf == out_cur. When out_cur > out_buf, the few + * bytes before out_cur are examined to see if there's any padding. If so, + * the base64 "quartet" is decoded, the pointer out_cur adjusted, and we + * naturally join the two streams together. Essentially the state of the + * base64 encoder when previously called is loaded from the buffer itself. + * + * Since base64 outputs in "quartets" of four characters, if out_cur > + * out_buf then out_cur must >= out_buf + 4. We are assuming we have a + * valid base64 stream that we generated ourselves. If this is not the + * case, just generate ordinarily; the concatenation of two base64 streams + * with padding is itself valid base64; in this case, caller beware. + */ + out_cur -= 4, jsub = 4; + nb = (uint8_t)base64_decode(out_cur, 4, joinbuf); + /* We know we have padding. Padding is only used to map 1 or 2 bytes to a + * base64 quartet, so nb == 1 or nb == 2. So this is a correct evaluation + * of the main for loop further below for this particular case, only on + * joinbuf, not ib. + */ + for (i=0; i> 3*6) & 0x3F]; + out_cur[j++] = _table[(triple >> 2*6) & 0x3F]; + out_cur[j++] = _table[(triple >> 1*6) & 0x3F]; + out_cur[j++] = _table[(triple >> 0*6) & 0x3F]; + nb = 0, triple = 0; + } + } + + switch (nb) { + case 2: + triple <<= 8; + out_cur[j++] = _table[(triple >> 3*6) & 0x3F]; + out_cur[j++] = _table[(triple >> 2*6) & 0x3F]; + out_cur[j++] = _table[(triple >> 1*6) & 0x3F]; + out_cur[j++] = '='; + break; + case 1: + triple <<= 2*8; + out_cur[j++] = _table[(triple >> 3*6) & 0x3F]; + out_cur[j++] = _table[(triple >> 2*6) & 0x3F]; + out_cur[j++] = '='; + out_cur[j++] = '='; + break; + } + + return j - jsub; +} + +/* Base64 Decoder + * -------------- + */ +static char _dectbl[256] = {}; + +static void _init_dectbl(void) +{ + static bool _done = false; + int i; + + if (_done) + return; + + for (i=0; i<64; ++i) + _dectbl[(uint8_t)_table[i]] = i; + + _done = true; +} + +size_t +base64_decode( + const char *in_buf, size_t in_buf_len, + void *out_buf) +{ + size_t i, written = 0; + uint8_t *ob = out_buf; + uint8_t nc = 0, np = 0; + uint32_t triple; + char c[4]; + + _init_dectbl(); + + for (i=0;ipos); + if (len > buf_len) + return -1; + + return base64_decode(s->str, s->pos, buf); +} + +/* Initialization + * -------------- + */ int init_util(void) { int i; @@ -433,3 +625,4 @@ int init_util(void) return 0; } +/* vim: set noet: */ -- 1.9.0