Reuse SSL sessions, added !reload and !restart command and some fixes.
This commit is contained in:
parent
02d73e0af6
commit
76ae9cc1ef
1 changed files with 268 additions and 27 deletions
295
zynk.c
295
zynk.c
|
|
@ -70,6 +70,7 @@ typedef struct {
|
|||
int rlen;
|
||||
SSL *ssl;
|
||||
int use_tls;
|
||||
int code_restart;
|
||||
} Session;
|
||||
|
||||
/* ---- forward declarations ---- */
|
||||
|
|
@ -555,6 +556,157 @@ static void tls_ctx_cleanup(void) {
|
|||
}
|
||||
}
|
||||
|
||||
/* ---- hot reload ---- */
|
||||
|
||||
#define ZYNK_RELOAD_FILE "/tmp/zynk_reload"
|
||||
|
||||
static int reload_read_line(FILE *f, char *buf, size_t sz) {
|
||||
if (!fgets(buf, sz, f)) return -1;
|
||||
size_t len = strlen(buf);
|
||||
while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) buf[--len] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int reload_save(Session *s) {
|
||||
if (!s->ssl || s->fd < 0) return -1;
|
||||
SSL_SESSION *sess = SSL_get1_session(s->ssl);
|
||||
if (!sess) return -1;
|
||||
int slen = i2d_SSL_SESSION(sess, NULL);
|
||||
if (slen <= 0) { SSL_SESSION_free(sess); return -1; }
|
||||
unsigned char *sbuf = malloc(slen);
|
||||
if (!sbuf) { SSL_SESSION_free(sess); return -1; }
|
||||
unsigned char *p = sbuf;
|
||||
i2d_SSL_SESSION(sess, &p);
|
||||
SSL_SESSION_free(sess);
|
||||
FILE *f = fopen(ZYNK_RELOAD_FILE, "w");
|
||||
if (!f) { free(sbuf); return -1; }
|
||||
fprintf(f, "%d\n%s\n%d\n%s\n%s\n%s\n%s\n%s\n%d\n%d\n",
|
||||
slen, s->host, s->port, s->nick, s->user, s->real,
|
||||
s->pass, s->auto_join, s->use_tls, tls_verify);
|
||||
for (int i = 0; i < slen; i++) fprintf(f, "%02x", sbuf[i]);
|
||||
fprintf(f, "\n");
|
||||
fclose(f);
|
||||
free(sbuf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int reload_try_restore(Session *s) {
|
||||
FILE *f = fopen(ZYNK_RELOAD_FILE, "r");
|
||||
if (!f) return -1;
|
||||
char line[1024];
|
||||
int slen = 0, port = 0, use_tls_f = 0, tls_verify_f = 0;
|
||||
char host[256] = "", nick[32] = "", user[32] = "", real[256] = "";
|
||||
char pass[256] = "", auto_join[256] = "";
|
||||
if (reload_read_line(f, line, sizeof line) < 0 || (slen = atoi(line)) <= 0 || slen > 65536) goto fail;
|
||||
if (reload_read_line(f, host, sizeof host) < 0 || !host[0]) goto fail;
|
||||
if (reload_read_line(f, line, sizeof line) < 0) goto fail;
|
||||
port = atoi(line);
|
||||
if (reload_read_line(f, nick, sizeof nick) < 0 || !nick[0]) goto fail;
|
||||
if (reload_read_line(f, user, sizeof user) < 0 || !user[0]) goto fail;
|
||||
if (reload_read_line(f, real, sizeof real) < 0) goto fail;
|
||||
if (reload_read_line(f, pass, sizeof pass) < 0) goto fail;
|
||||
if (reload_read_line(f, auto_join, sizeof auto_join) < 0) goto fail;
|
||||
if (reload_read_line(f, line, sizeof line) < 0) goto fail;
|
||||
use_tls_f = atoi(line);
|
||||
if (reload_read_line(f, line, sizeof line) < 0) goto fail;
|
||||
tls_verify_f = atoi(line);
|
||||
unsigned char *sbuf = malloc(slen);
|
||||
if (!sbuf) goto fail;
|
||||
for (int i = 0; i < slen; i++) {
|
||||
unsigned int byte;
|
||||
if (fscanf(f, "%02x", &byte) != 1) { free(sbuf); goto fail; }
|
||||
sbuf[i] = (unsigned char)byte;
|
||||
}
|
||||
fclose(f);
|
||||
unlink(ZYNK_RELOAD_FILE);
|
||||
snprintf(s->host, sizeof s->host, "%s", host);
|
||||
s->port = port;
|
||||
snprintf(s->nick, sizeof s->nick, "%s", nick);
|
||||
snprintf(s->user, sizeof s->user, "%s", user);
|
||||
snprintf(s->real, sizeof s->real, "%s", real);
|
||||
if (pass[0]) snprintf(s->pass, sizeof s->pass, "%s", pass);
|
||||
snprintf(s->auto_join, sizeof s->auto_join, "%s", auto_join);
|
||||
s->use_tls = use_tls_f;
|
||||
tls_verify = tls_verify_f;
|
||||
if (!use_tls_f) { free(sbuf); return -1; }
|
||||
if (tls_init() < 0) { free(sbuf); return -1; }
|
||||
s->ssl = SSL_new(tls_ctx);
|
||||
if (!s->ssl) { free(sbuf); return -1; }
|
||||
const unsigned char *sp = sbuf;
|
||||
SSL_SESSION *sess = d2i_SSL_SESSION(NULL, &sp, slen);
|
||||
free(sbuf);
|
||||
if (!sess) { SSL_free(s->ssl); s->ssl = NULL; return -1; }
|
||||
SSL_set_fd(s->ssl, s->fd);
|
||||
SSL_set_tlsext_host_name(s->ssl, s->host);
|
||||
if (tls_verify) SSL_set1_host(s->ssl, s->host);
|
||||
SSL_set_session(s->ssl, sess);
|
||||
SSL_SESSION_free(sess);
|
||||
int fl = fcntl(s->fd, F_GETFL, 0);
|
||||
fcntl(s->fd, F_SETFL, fl & ~O_NONBLOCK);
|
||||
int r = SSL_do_handshake(s->ssl);
|
||||
fcntl(s->fd, F_SETFL, fl | O_NONBLOCK);
|
||||
if (r != 1) { SSL_free(s->ssl); s->ssl = NULL; return -1; }
|
||||
if (tls_verify && SSL_get_verify_result(s->ssl) != X509_V_OK) {
|
||||
SSL_free(s->ssl); s->ssl = NULL; return -1;
|
||||
}
|
||||
s->connected = 1;
|
||||
FILE *cf = fopen("/tmp/zynk_code_restart", "r");
|
||||
if (cf) {
|
||||
fclose(cf);
|
||||
unlink("/tmp/zynk_code_restart");
|
||||
s->code_restart = 1;
|
||||
}
|
||||
log_stamp(); fprintf(stderr, CLR_GREEN "TLS session resumed for %s:%d" CLR_RESET "\n", s->host, s->port);
|
||||
return 0;
|
||||
fail:
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void irc_part_all(Session *s) {
|
||||
for (int i = 0; i < nchans; i++) {
|
||||
if (chans[i].name[0])
|
||||
net_send(s, "PART %s\r\n", chans[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
static void reload_do(Session *s, const char *reply_target) {
|
||||
if (!s->ssl || s->fd < 0) {
|
||||
irc_msg(s, reply_target, "Cannot reload: not connected with TLS");
|
||||
return;
|
||||
}
|
||||
if (reload_save(s) < 0) {
|
||||
irc_msg(s, reply_target, "Failed to save session state");
|
||||
return;
|
||||
}
|
||||
irc_msg(s, reply_target, "Reloading...");
|
||||
struct timespec ts_short = { .tv_sec = 0, .tv_nsec = 100000000 };
|
||||
nanosleep(&ts_short, NULL);
|
||||
irc_part_all(s);
|
||||
int old_fl = fcntl(s->fd, F_GETFL, 0);
|
||||
fcntl(s->fd, F_SETFL, old_fl & ~O_NONBLOCK);
|
||||
net_send(s, "QUIT :restarting\r\n");
|
||||
struct timespec ts_quit = { .tv_sec = 0, .tv_nsec = 500000000 };
|
||||
nanosleep(&ts_quit, NULL);
|
||||
fcntl(s->fd, F_SETFL, old_fl & ~O_CLOEXEC);
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
unlink(ZYNK_RELOAD_FILE);
|
||||
irc_msg(s, reply_target, "Fork failed, continuing");
|
||||
return;
|
||||
}
|
||||
if (pid == 0) {
|
||||
char fdstr[16];
|
||||
snprintf(fdstr, sizeof fdstr, "%d", s->fd);
|
||||
setenv("ZYNK_RESTORE_FD", fdstr, 1);
|
||||
char self[4096];
|
||||
ssize_t n = readlink("/proc/self/exe", self, sizeof self - 1);
|
||||
if (n > 0) { self[n] = 0; execl(self, "zynk", NULL); }
|
||||
_exit(1);
|
||||
}
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
/* ---- signal handling ---- */
|
||||
|
||||
static volatile sig_atomic_t running = 1;
|
||||
|
|
@ -951,6 +1103,36 @@ static void ai_ask(Session *s, const char *question, const char *reply_target, c
|
|||
log_stamp(); fprintf(stderr, CLR_BLUE "AI started (PID %d, ID %lld) from %s:" CLR_RESET " %s\n", pid, id, src_nick, question);
|
||||
}
|
||||
|
||||
static int try_compile_and_restart(Session *s, const char *target) {
|
||||
log_stamp(); fprintf(stderr, CLR_GREEN "Code change detected, compiling..." CLR_RESET "\n");
|
||||
int status = system("make 2>/tmp/zynk_make_err");
|
||||
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
|
||||
irc_msg(s, target, "Code compiled successfully, restarting...");
|
||||
log_stamp(); fprintf(stderr, CLR_GREEN "Compilation successful, restarting..." CLR_RESET "\n");
|
||||
FILE *cf = fopen("/tmp/zynk_code_restart", "w");
|
||||
if (cf) { fprintf(cf, "%s\n", target); fclose(cf); }
|
||||
reload_do(s, target);
|
||||
return 1;
|
||||
} else {
|
||||
char errbuf[512];
|
||||
FILE *f = fopen("/tmp/zynk_make_err", "r");
|
||||
if (f) {
|
||||
size_t n = fread(errbuf, 1, sizeof errbuf - 1, f);
|
||||
errbuf[n] = 0;
|
||||
fclose(f);
|
||||
char *nl = strchr(errbuf, '\n');
|
||||
if (nl) *nl = 0;
|
||||
} else {
|
||||
snprintf(errbuf, sizeof errbuf, "unknown error");
|
||||
}
|
||||
char msg[640];
|
||||
snprintf(msg, sizeof msg, "Compilation failed: %s", errbuf);
|
||||
irc_msg(s, target, msg);
|
||||
log_stamp(); fprintf(stderr, CLR_RED "Compilation failed:" CLR_RESET " %s\n", errbuf);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void ai_check_completion(Session *s) {
|
||||
if (pending_ai_pid <= 0) return;
|
||||
int wstatus;
|
||||
|
|
@ -960,6 +1142,10 @@ static void ai_check_completion(Session *s) {
|
|||
pending_ai_pid = 0;
|
||||
char *nick = ai_get_nick(done_id);
|
||||
char *result = ai_get_answer(done_id);
|
||||
int is_code_change = 0;
|
||||
char *question = ai_get_question(done_id);
|
||||
if (question && strncmp(question, "[CODE CHANGE REQUEST", 20) == 0) is_code_change = 1;
|
||||
free(question);
|
||||
if (result) {
|
||||
char *answer = result;
|
||||
char *sep = strchr(result, '\037');
|
||||
|
|
@ -987,6 +1173,7 @@ static void ai_check_completion(Session *s) {
|
|||
chunks++;
|
||||
}
|
||||
log_stamp(); fprintf(stderr, CLR_BLUE "AI done PID %d from %s: %zu chars sent as %d chunks to %s" CLR_RESET "\n", done_pid, nick ? nick : "?", alen, chunks, target);
|
||||
if (is_code_change) try_compile_and_restart(s, target);
|
||||
}
|
||||
free(result);
|
||||
ai_reset(done_id);
|
||||
|
|
@ -1194,7 +1381,7 @@ static int cmd_help(Session *s, const char *msg, const char *reply_target, const
|
|||
if (rate_limit_check(src_nick) < 0) return 1;
|
||||
char buf[512];
|
||||
snprintf(buf, sizeof buf,
|
||||
"Commands: !help, !version, !ping, !weather [city], !forecast [city], !stock <SYMBOL>[,SYMBOL...], !ai <question>, !code <change> (ops), !quit/!die (ops)");
|
||||
"Commands: !help, !version, !ping, !weather [city], !forecast [city], !stock <SYMBOL>[,SYMBOL...], !ai <question>, !code <change> (ops), !reload (ops), !restart (ops), !quit/!die (ops)");
|
||||
irc_msg(s, reply_target, buf);
|
||||
log_stamp(); fprintf(stderr, CLR_CYAN "HELP from %s in %s" CLR_RESET "\n", src_nick, reply_target);
|
||||
return 1;
|
||||
|
|
@ -1392,6 +1579,29 @@ static int cmd_code(Session *s, const char *msg, const char *reply_target, const
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int cmd_reload(Session *s, const char *msg, const char *reply_target, const char *src_nick, const char *tgt) {
|
||||
if (strcmp(msg, "!reload") != 0) return 0;
|
||||
if (tgt[0] == '#' && !chan_is_op(tgt, src_nick)) {
|
||||
irc_msg(s, reply_target, "You need op to reload");
|
||||
return 1;
|
||||
}
|
||||
if (rate_limit_check(src_nick) < 0) return 1;
|
||||
reload_do(s, reply_target);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int cmd_restart(Session *s, const char *msg, const char *reply_target, const char *src_nick, const char *tgt) {
|
||||
if (strcmp(msg, "!restart") != 0) return 0;
|
||||
if (tgt[0] == '#' && !chan_is_op(tgt, src_nick)) {
|
||||
irc_msg(s, reply_target, "You need op to restart me");
|
||||
return 1;
|
||||
}
|
||||
if (rate_limit_check(src_nick) < 0) return 1;
|
||||
irc_msg(s, reply_target, "Restarting...");
|
||||
reload_do(s, reply_target);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void cmd_greeting_or_chat(Session *s, const char *msg, const char *reply_target, const char *src_nick) {
|
||||
int nicklen = strlen(s->nick);
|
||||
const char *cmd = msg;
|
||||
|
|
@ -1453,6 +1663,8 @@ static void handle_privmsg(Session *s, const char *params, const char *src_nick)
|
|||
if (cmd_stock(s, msg, reply_target, src_nick)) return;
|
||||
if (cmd_ai(s, msg, reply_target, src_nick)) return;
|
||||
if (cmd_code(s, msg, reply_target, src_nick, tgt)) return;
|
||||
if (cmd_reload(s, msg, reply_target, src_nick, tgt)) return;
|
||||
if (cmd_restart(s, msg, reply_target, src_nick, tgt)) return;
|
||||
cmd_greeting_or_chat(s, msg, reply_target, src_nick);
|
||||
}
|
||||
|
||||
|
|
@ -1616,6 +1828,21 @@ static void irc_handle(Session *s, const char *raw) {
|
|||
}
|
||||
free(copy);
|
||||
}
|
||||
if (s->code_restart) {
|
||||
s->code_restart = 0;
|
||||
char *c2 = strdup(s->auto_join);
|
||||
if (c2) {
|
||||
char *sv;
|
||||
for (char *t = strtok_r(c2, ",", &sv); t; t = strtok_r(NULL, ",", &sv)) {
|
||||
while (*t == ' ') t++;
|
||||
char *e = t + strlen(t);
|
||||
while (e > t && e[-1] == ' ') e--;
|
||||
*e = 0;
|
||||
if (*t) irc_msg(s, t, "Code update done, I'm back!");
|
||||
}
|
||||
free(c2);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (strcmp(cmd_buf, "PRIVMSG") == 0) { handle_privmsg(s, params, src_nick); return; }
|
||||
|
|
@ -1791,39 +2018,53 @@ int main(int argc, char **argv) {
|
|||
snprintf(s.real, sizeof s.real, "zynk");
|
||||
snprintf(s.auto_join, sizeof s.auto_join, "%s", joinbuf);
|
||||
s.fd = -1;
|
||||
const char *restore_fd_env = getenv("ZYNK_RESTORE_FD");
|
||||
if (restore_fd_env) {
|
||||
s.fd = atoi(restore_fd_env);
|
||||
unsetenv("ZYNK_RESTORE_FD");
|
||||
}
|
||||
s.use_tls = use_tls;
|
||||
if (passbuf[0]) snprintf(s.pass, sizeof s.pass, "%s", passbuf);
|
||||
if (use_tls && tls_init() < 0) {
|
||||
fprintf(stderr, CLR_RED "TLS init failed" CLR_RESET "\n");
|
||||
db_close_cleanup();
|
||||
sqlite_cleanup();
|
||||
return 1;
|
||||
}
|
||||
signal(SIGINT, handle_sigterm);
|
||||
signal(SIGTERM, handle_sigterm);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
s.fd = net_connect(s.host, s.port);
|
||||
if (s.fd < 0) {
|
||||
fprintf(stderr, CLR_RED "Could not connect to %s:%d" CLR_RESET "\n", s.host, s.port);
|
||||
tls_ctx_cleanup();
|
||||
db_close_cleanup();
|
||||
sqlite_cleanup();
|
||||
return 1;
|
||||
int reloaded = 0;
|
||||
if (use_tls) {
|
||||
reloaded = (reload_try_restore(&s) == 0);
|
||||
if (reloaded) {
|
||||
log_stamp(); fprintf(stderr, CLR_GREEN "Hot reload: TLS session resumed" CLR_RESET "\n");
|
||||
}
|
||||
}
|
||||
if (s.use_tls && tls_handshake(&s) < 0) {
|
||||
fprintf(stderr, CLR_RED "TLS handshake failed" CLR_RESET "\n");
|
||||
close(s.fd);
|
||||
tls_ctx_cleanup();
|
||||
db_close_cleanup();
|
||||
sqlite_cleanup();
|
||||
return 1;
|
||||
if (!reloaded) {
|
||||
if (use_tls && tls_init() < 0) {
|
||||
fprintf(stderr, CLR_RED "TLS init failed" CLR_RESET "\n");
|
||||
db_close_cleanup();
|
||||
sqlite_cleanup();
|
||||
return 1;
|
||||
}
|
||||
s.fd = net_connect(s.host, s.port);
|
||||
if (s.fd < 0) {
|
||||
fprintf(stderr, CLR_RED "Could not connect to %s:%d" CLR_RESET "\n", s.host, s.port);
|
||||
tls_ctx_cleanup();
|
||||
db_close_cleanup();
|
||||
sqlite_cleanup();
|
||||
return 1;
|
||||
}
|
||||
if (s.use_tls && tls_handshake(&s) < 0) {
|
||||
fprintf(stderr, CLR_RED "TLS handshake failed" CLR_RESET "\n");
|
||||
close(s.fd);
|
||||
tls_ctx_cleanup();
|
||||
db_close_cleanup();
|
||||
sqlite_cleanup();
|
||||
return 1;
|
||||
}
|
||||
int fl = fcntl(s.fd, F_GETFL, 0);
|
||||
fcntl(s.fd, F_SETFL, fl | O_NONBLOCK);
|
||||
log_stamp(); fprintf(stderr, CLR_GREEN "Connecting to %s:%d as %s..." CLR_RESET "\n", s.host, s.port, s.nick);
|
||||
if (s.pass[0]) net_send(&s, "PASS %s\r\n", s.pass);
|
||||
irc_nick(&s, s.nick);
|
||||
net_send(&s, "USER %s 0 * :%s\r\n", s.user, s.real);
|
||||
}
|
||||
int fl = fcntl(s.fd, F_GETFL, 0);
|
||||
fcntl(s.fd, F_SETFL, fl | O_NONBLOCK);
|
||||
log_stamp(); fprintf(stderr, CLR_GREEN "Connecting to %s:%d as %s..." CLR_RESET "\n", s.host, s.port, s.nick);
|
||||
if (s.pass[0]) net_send(&s, "PASS %s\r\n", s.pass);
|
||||
irc_nick(&s, s.nick);
|
||||
net_send(&s, "USER %s 0 * :%s\r\n", s.user, s.real);
|
||||
running = 1;
|
||||
pending_ai_pid = 0;
|
||||
while (running) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue