Reuse SSL sessions, added !reload and !restart command and some fixes.

This commit is contained in:
Johannes Findeisen 2026-07-19 00:14:30 +02:00
commit 76ae9cc1ef

295
zynk.c
View file

@ -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) {