zynk/cmd_zynk.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

105 lines
3.6 KiB
C

#ifndef CMD_ZYNK_H
#define CMD_ZYNK_H
#include "zynk.h"
/*
* cmd_zynk - Handle the "!zynk" key-value store command.
*
* Processes three forms: bare "!zynk" (returns random entry),
* "!zynk <key>" (looks up value), and "!zynk <key> = <value>" (stores value).
* Also handles "!zynk!" prefix for overwriting existing keys (requires channel
* op). Keys are lowercased. Rate-limited. Blocked re-adds without "!" prefix
* to prevent accidental overwrites.
*
* 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 the command was matched (regardless of success), 0 otherwise.
*/
int cmd_zynk(Session *s, const char *msg, const char *reply_target, const char *src_nick, const char *tgt) {
if (strncmp(msg, "!zynk", 5) != 0) return 0;
if (msg[5] != '\0' && msg[5] != ' ' && msg[5] != '!') return 0;
if (rate_limit_check(src_nick, reply_target, current_userhost) < 0) return 1;
if (msg[5] == '!' && msg[6] != '\0' && msg[6] != ' ') return 0;
int force = 0;
const char *rest;
if (msg[5] == '\0') {
char *rand = db_get_random();
if (rand) {
irc_reply(s, reply_target, src_nick, rand);
log_stamp(); fprintf(stderr, CLR_MAGENTA "ZYNK %s from %s:" CLR_RESET " random %s\n", reply_target, src_nick, rand);
free(rand);
} else {
irc_reply(s, reply_target, src_nick, "No zynkss saved yet.");
}
return 1;
} else if (msg[5] == '!') {
if (msg[6] == '\0') {
irc_reply(s, reply_target, src_nick, "Usage: !zynk! <key> = <value>");
return 1;
}
force = 1;
rest = msg + 7;
} else {
rest = msg + 6;
}
while (*rest == ' ') rest++;
if (!*rest) {
irc_reply(s, reply_target, src_nick, "Usage: !zynk <key> [= <value>]");
return 1;
}
const char *eq = strstr(rest, " = ");
char resp[1024];
if (eq) {
char key[256];
size_t kl = eq - rest;
if (kl == 0) { irc_reply(s, reply_target, src_nick, "Key cannot be empty"); return 1; }
if (kl >= sizeof key) kl = sizeof key - 1;
memcpy(key, rest, kl); key[kl] = 0;
for (char *p = key; *p; p++) *p = tolower((unsigned char)*p);
if (!force) {
char *existing = db_get_raw(key);
if (existing) {
free(existing);
irc_reply(s, reply_target, src_nick, "zynk already exists (use !zynk! to overwrite)");
log_stamp(); fprintf(stderr, CLR_MAGENTA "ZYNK %s from %s:" CLR_RESET " blocked re-add of %s\n", reply_target, src_nick, key);
return 1;
}
} else {
if (tgt[0] == '#' && !chan_is_op(tgt, src_nick)) {
irc_reply(s, reply_target, src_nick, "You need op to overwrite a zynk");
return 1;
}
}
const char *val = eq + 3;
while (*val == ' ') val++;
if (db_set(key, val, src_nick) == 0)
snprintf(resp, sizeof resp, "%s = %s (saved)", key, val);
else
snprintf(resp, sizeof resp, "Error saving %s", key);
} else {
char key[256];
snprintf(key, sizeof key, "%s", rest);
for (char *p = key; *p; p++) *p = tolower((unsigned char)*p);
char *end = key + strlen(key);
while (end > key && end[-1] == ' ') end--;
*end = 0;
char *val = db_get(key);
if (val) {
snprintf(resp, sizeof resp, "%s: %s", key, val);
free(val);
} else {
snprintf(resp, sizeof resp, "%s: not found", key);
}
}
irc_reply(s, reply_target, src_nick, resp);
log_stamp(); fprintf(stderr, CLR_MAGENTA "ZYNK %s from %s:" CLR_RESET " %s\n", reply_target, src_nick, resp);
return 1;
}
#endif