- Added secure_state_open(): hardened file open with O_CLOEXEC, O_NOFOLLOW, mode 0600, regular-file and ownership checks. Prevents symlink attacks, TOCTOU races, and world-writable temp files. - Replaced all fopen() calls for temp state files with secure_state_open() + fdopen() across hot reload, compile-restart, and rebuild paths. - Hardened TLS initialization: enforces minimum TLS 1.2, disables compression, checks SSL_CTX_set_default_verify_paths() return value. - CMake compiler/linker hardening: -Wformat=2, -Wstrict-prototypes, -fstack-protector-strong, _FORTIFY_SOURCE=3, RELRO, noexecstack. - Added !calc input validation: max 256 chars, numeric-only characters. - Added !stock symbol validation: alphanumerics plus .-^= only. - Fixed !quit, !reload, !restart to require channel context (DMs rejected). - Fixed log timestamp format: %y (2-digit) to %Y (4-digit year). - Fixed asprintf return value check in !forecast error paths. - Fixed !reload fork-in-place mode: only saves TLS state when exec_new=1. - Cleaned up reload state file on failure and in fork child. - Fixed git SSH-to-HTTPS URL conversion in !gitlog and !changelog. - AI plan mode no longer passes --dangerously-skip-permissions. - Added IRC buffer overflow protection: oversized lines are dropped. - Added doc comments to ZYNK_RELOAD_FILE, secure_state_open(), RateLimitEntry, and rate_limits.
93 lines
4.1 KiB
C
93 lines
4.1 KiB
C
#ifndef CMD_REBUILD_H
|
|
#define CMD_REBUILD_H
|
|
|
|
#include "zynk.h"
|
|
|
|
/*
|
|
* cmd_rebuild - Handle the "!rebuild" command.
|
|
*
|
|
* Requires channel op and must be used in a channel. Optionally runs
|
|
* "git pull" if the argument "pull" is provided (e.g. "!rebuild pull").
|
|
* Always runs "make clean" and "make" in sequence. If all steps succeed,
|
|
* triggers a hot reload via reload_do(). If any step fails, reports the
|
|
* error from the build output. Rate-limited.
|
|
*
|
|
* Parameters:
|
|
* s - The IRC session.
|
|
* msg - The raw message text.
|
|
* reply_target - Channel or nick to reply to.
|
|
* src_nick - The nick of the user who sent the command.
|
|
* tgt - The message target (channel or nick).
|
|
*
|
|
* Returns: 1 if matched, 0 otherwise.
|
|
*/
|
|
int cmd_rebuild(Session *s, const char *msg, const char *reply_target, const char *src_nick, const char *tgt) {
|
|
int do_pull = (strcmp(msg, "!rebuild pull") == 0);
|
|
if (strcmp(msg, "!rebuild") != 0 && !do_pull) return 0;
|
|
if (tgt[0] != '#') {
|
|
irc_msg(s, reply_target, "!rebuild can only be used in a channel");
|
|
return 1;
|
|
}
|
|
if (!chan_is_op(tgt, src_nick)) {
|
|
irc_msg(s, reply_target, "You need op to rebuild");
|
|
return 1;
|
|
}
|
|
if (rate_limit_check(src_nick, reply_target) < 0) return 1;
|
|
irc_msg(s, reply_target, do_pull ? "Pulling and rebuilding..." : "Rebuilding...");
|
|
log_stamp(); fprintf(stderr, CLR_GREEN "REBUILD from %s in %s (pull=%d)" CLR_RESET "\n", src_nick, reply_target, do_pull);
|
|
int status;
|
|
char errbuf[512];
|
|
int efd = secure_state_open("/tmp/zynk_rebuild_err", O_WRONLY | O_CREAT | O_TRUNC);
|
|
if (efd < 0) {
|
|
irc_msg(s, reply_target, "Rebuild failed: cannot create secure build log");
|
|
return 1;
|
|
}
|
|
close(efd);
|
|
if (do_pull) {
|
|
status = system("git pull 2>/tmp/zynk_rebuild_err");
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
|
char msg_buf[640];
|
|
efd = secure_state_open("/tmp/zynk_rebuild_err", O_RDONLY);
|
|
FILE *f = efd >= 0 ? fdopen(efd, "r") : NULL;
|
|
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");
|
|
snprintf(msg_buf, sizeof msg_buf, "Rebuild failed (git pull): %s", errbuf);
|
|
irc_msg(s, reply_target, msg_buf);
|
|
log_stamp(); fprintf(stderr, CLR_RED "REBUILD git pull failed:" CLR_RESET " %s\n", errbuf);
|
|
return 1;
|
|
}
|
|
}
|
|
status = system("make clean 2>/tmp/zynk_rebuild_err");
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
|
char msg_buf[640];
|
|
efd = secure_state_open("/tmp/zynk_rebuild_err", O_RDONLY);
|
|
FILE *f = efd >= 0 ? fdopen(efd, "r") : NULL;
|
|
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");
|
|
snprintf(msg_buf, sizeof msg_buf, "Rebuild failed (make clean): %s", errbuf);
|
|
irc_msg(s, reply_target, msg_buf);
|
|
log_stamp(); fprintf(stderr, CLR_RED "REBUILD make clean failed:" CLR_RESET " %s\n", errbuf);
|
|
return 1;
|
|
}
|
|
status = system("make 2>/tmp/zynk_rebuild_err");
|
|
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
|
|
irc_msg(s, reply_target, "Build successful, restarting...");
|
|
log_stamp(); fprintf(stderr, CLR_GREEN "REBUILD successful, restarting..." CLR_RESET "\n");
|
|
int cfd = secure_state_open("/tmp/zynk_code_restart", O_WRONLY | O_CREAT | O_TRUNC);
|
|
FILE *cf = cfd >= 0 ? fdopen(cfd, "w") : NULL;
|
|
if (cf) { fprintf(cf, "%s\n", reply_target); fclose(cf); }
|
|
reload_do(s, reply_target, 1);
|
|
} else {
|
|
efd = secure_state_open("/tmp/zynk_rebuild_err", O_RDONLY);
|
|
FILE *f = efd >= 0 ? fdopen(efd, "r") : NULL;
|
|
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_buf[640];
|
|
snprintf(msg_buf, sizeof msg_buf, "Rebuild failed (make): %s", errbuf);
|
|
irc_msg(s, reply_target, msg_buf);
|
|
log_stamp(); fprintf(stderr, CLR_RED "REBUILD make failed:" CLR_RESET " %s\n", errbuf);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#endif
|