zynk/cmd_calc.h
hanez 3b97b4d902 0.41.2 Implement comprehensive flood protection for IRC bot.
Phase 1: Incoming Protocol-Level Protection
- Added MAX_LINES_PER_POLL = 50 constant to limit lines processed per
  poll iteration in irc_feed(), preventing burst flooding.
- Added poll_lines_left counter to Session struct, reset each poll cycle.
- Changed RateLimitEntry to track by user@host instead of nick, preventing
  trivial bypass via nick cycling.
- Parse userhost from IRC prefix (nick!user@host) in irc_handle().
- Added violation tracking to RateLimitEntry for escalating penalties.

Phase 2: Outgoing Message Throttling
- Added OUTGOING_DELAY_MS = 100 constant for minimum delay between sends.
- Modified net_send() with timestamp-based throttling using clock_gettime().
- Added 200ms delay between lines in !gitlog output (cmd_gitlog.h).
- Added 150ms delay between AI response chunks (ai.h).

Phase 3: Rate Limit Improvements
- Increased RATE_LIMIT_MAX from 8 to 32 for larger tracking buffer.
- Added TELL_DELIVER_MAX = 5 to limit messages per nick delivery.
- Added AI_COOLDOWN_SECS = 5 to prevent rapid AI query re-submission.

Phase 4: Command-Specific Fixes
- Added rate_limit_check() to cmd_greeting_or_chat.h for casual chat.
- Limited tell_deliver() to 5 messages per nick, excess stays queued.
- Added 5-second cooldown after AI query completion before next query.

Files modified: zynk.h, zynk.c, irc.h, net.h, ai.h, db.h, and all
19 command header files to pass current_userhost to rate_limit_check().
2026-07-25 20:37:07 +02:00

96 lines
3.2 KiB
C

#ifndef CMD_CALC_H
#define CMD_CALC_H
#include "zynk.h"
/*
* cmd_calc - Handle the "!calc <expression>" command.
*
* Evaluates a mathematical expression using bc(1) with the -l math
* library (providing pi, sin, cos, etc.). Forks a child process,
* pipes the expression to bc's stdin, and reads the result from stdout.
* Defines pi() as a built-in. 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.
*
* Returns: 1 if matched, 0 otherwise.
*/
int cmd_calc(Session *s, const char *msg, const char *reply_target, const char *src_nick) {
if (strncmp(msg, "!calc", 5) != 0) return 0;
if (msg[5] != ' ' && msg[5] != '\0') return 0;
if (rate_limit_check(src_nick, reply_target, current_userhost) < 0) return 1;
const char *rest = msg[5] == ' ' ? msg + 6 : "";
while (*rest == ' ') rest++;
if (!*rest) {
irc_reply(s, reply_target, src_nick, "Usage: !calc <expression> (e.g. !calc 2+2, !calc (3*7)+1)");
return 1;
}
size_t expr_len = strlen(rest);
if (expr_len > 256) {
irc_reply(s, reply_target, src_nick, "Calc: expression too long");
return 1;
}
for (const unsigned char *p = (const unsigned char *)rest; *p; p++) {
if (!isdigit(*p) && !strchr(".()+-*/%^ \t", *p)) {
irc_reply(s, reply_target, src_nick, "Calc: only numeric expressions are allowed");
return 1;
}
}
int in_pipe[2], out_pipe[2];
if (pipe(in_pipe) < 0 || pipe(out_pipe) < 0) { irc_reply(s, reply_target, src_nick, "Calc: internal error"); return 1; }
pid_t pid = fork();
if (pid < 0) { close(in_pipe[0]); close(in_pipe[1]); close(out_pipe[0]); close(out_pipe[1]); irc_reply(s, reply_target, src_nick, "Calc: fork failed"); return 1; }
if (pid == 0) {
close(in_pipe[1]);
close(out_pipe[0]);
dup2(in_pipe[0], STDIN_FILENO);
close(in_pipe[0]);
dup2(out_pipe[1], STDOUT_FILENO);
close(out_pipe[1]);
execlp("bc", "bc", "-l", (char *)NULL);
_exit(127);
}
close(in_pipe[0]);
close(out_pipe[1]);
const char *pidef = "define pi() { return 3.14159265358979323844; }\n";
if (dprintf(in_pipe[1], "%s%s\n", pidef, rest) < 0) {
close(in_pipe[1]);
close(out_pipe[0]);
kill(pid, SIGTERM);
waitpid(pid, NULL, 0);
irc_reply(s, reply_target, src_nick, "Calc: evaluation failed");
return 1;
}
close(in_pipe[1]);
char buf[512], tmp[256];
size_t len = 0;
ssize_t n;
while ((n = read(out_pipe[0], tmp, sizeof tmp - 1)) > 0) {
size_t avail = sizeof buf - 1 - len;
if (avail > 0) {
size_t copy = (size_t)n < avail ? (size_t)n : avail;
memcpy(buf + len, tmp, copy);
len += copy;
}
}
close(out_pipe[0]);
int status;
waitpid(pid, &status, 0);
while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) len--;
buf[len] = 0;
char *nl = strchr(buf, '\n');
if (nl) *nl = 0;
if (len == 0) {
irc_reply(s, reply_target, src_nick, "Calc: no result (check expression syntax)");
} else {
irc_reply(s, reply_target, src_nick, buf);
}
log_stamp(); fprintf(stderr, CLR_YELLOW "CALC %s from %s:" CLR_RESET " %s\n", reply_target, src_nick, rest);
return 1;
}
#endif