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().
105 lines
3.6 KiB
C
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
|