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().
155 lines
4.9 KiB
C
155 lines
4.9 KiB
C
#ifndef NET_H
|
|
#define NET_H
|
|
|
|
#include "zynk.h"
|
|
|
|
/* ---- outgoing throttle ---- */
|
|
|
|
static struct timespec last_send_time;
|
|
static int throttle_initialized;
|
|
|
|
/* ---- network I/O ---- */
|
|
|
|
/*
|
|
* net_connect - Establish a TCP connection to a remote host.
|
|
*
|
|
* Performs DNS resolution via getaddrinfo() with AI_ADDRCONFIG (restricts
|
|
* to address families available on the local system), then iterates through
|
|
* resolved addresses trying socket()+connect() on each. The socket is
|
|
* non-blocking by default since this function doesn't set O_NONBLOCK.
|
|
*
|
|
* Parameters:
|
|
* host - The hostname or IP address to connect to.
|
|
* port - The TCP port number.
|
|
*
|
|
* Returns: A connected file descriptor on success, or -1 on failure.
|
|
*/
|
|
int net_connect(const char *host, int port) {
|
|
struct addrinfo hints, *ai;
|
|
memset(&hints, 0, sizeof hints);
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_flags = AI_ADDRCONFIG;
|
|
char pbuf[16];
|
|
snprintf(pbuf, sizeof pbuf, "%d", port);
|
|
int err = getaddrinfo(host, pbuf, &hints, &ai);
|
|
if (err) { fprintf(stderr, CLR_RED "DNS:" CLR_RESET " %s\n", gai_strerror(err)); return -1; }
|
|
int fd = -1;
|
|
for (struct addrinfo *rp = ai; rp; rp = rp->ai_next) {
|
|
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
|
if (fd < 0) continue;
|
|
if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) break;
|
|
close(fd); fd = -1;
|
|
}
|
|
freeaddrinfo(ai);
|
|
return fd;
|
|
}
|
|
|
|
/*
|
|
* net_read - Read data from the session's socket (TLS-aware).
|
|
*
|
|
* If the session has an active SSL connection, reads via SSL_read();
|
|
* otherwise uses plain read(). Translates SSL_ERROR_WANT_READ/WANT_WRITE
|
|
* into errno=EAGAIN for consistent non-blocking behavior. Returns 0 on
|
|
* clean TLS shutdown (SSL_ERROR_ZERO_RETURN).
|
|
*
|
|
* Parameters:
|
|
* s - The IRC session with fd and optional SSL context.
|
|
* buf - Destination buffer for the read data.
|
|
* size - Maximum number of bytes to read.
|
|
*
|
|
* Returns: Number of bytes read, 0 on EOF/shutdown, -1 on error (check
|
|
* errno for EAGAIN to distinguish transient from fatal errors).
|
|
*/
|
|
int net_read(Session *s, char *buf, int size) {
|
|
if (s->ssl) {
|
|
int r = SSL_read(s->ssl, buf, size);
|
|
if (r > 0) return r;
|
|
int e = SSL_get_error(s->ssl, r);
|
|
if (e == SSL_ERROR_WANT_READ || e == SSL_ERROR_WANT_WRITE) { errno = EAGAIN; return -1; }
|
|
if (e == SSL_ERROR_ZERO_RETURN) return 0;
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
return read(s->fd, buf, size);
|
|
}
|
|
|
|
/*
|
|
* net_write - Write data to the session's socket (TLS-aware).
|
|
*
|
|
* If the session has an active SSL connection, writes via SSL_write();
|
|
* otherwise uses plain write(). Translates SSL_ERROR_WANT_READ/WANT_WRITE
|
|
* into errno=EAGAIN for consistent non-blocking behavior.
|
|
*
|
|
* Parameters:
|
|
* s - The IRC session with fd and optional SSL context.
|
|
* buf - Source buffer containing data to send.
|
|
* size - Number of bytes to write.
|
|
*
|
|
* Returns: Number of bytes written on success, -1 on error.
|
|
*/
|
|
int net_write(Session *s, const char *buf, int size) {
|
|
if (s->ssl) {
|
|
int r = SSL_write(s->ssl, buf, size);
|
|
if (r > 0) return r;
|
|
int e = SSL_get_error(s->ssl, r);
|
|
if (e == SSL_ERROR_WANT_READ || e == SSL_ERROR_WANT_WRITE) { errno = EAGAIN; return -1; }
|
|
return -1;
|
|
}
|
|
return write(s->fd, buf, size);
|
|
}
|
|
|
|
/*
|
|
* net_send - Format and send an IRC protocol message with retry logic.
|
|
*
|
|
* Uses vsnprintf to format the message into a 4096-byte stack buffer, then
|
|
* writes it via net_write() in a loop to handle partial writes. Retries on
|
|
* EINTR (signal interruption) and EAGAIN (would-block). The caller should
|
|
* include "\r\n" in the format string per IRC protocol requirements.
|
|
*
|
|
* Parameters:
|
|
* s - The IRC session.
|
|
* fmt - printf-style format string for the message.
|
|
* ... - Format arguments.
|
|
*
|
|
* Returns: Number of bytes sent on success, 0 if nothing to send,
|
|
* -1 on truncation or write failure.
|
|
*/
|
|
int net_send(Session *s, const char *fmt, ...) {
|
|
char buf[4096];
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
int n = vsnprintf(buf, sizeof buf, fmt, ap);
|
|
va_end(ap);
|
|
if (n <= 0) return 0;
|
|
if ((size_t)n >= sizeof buf) return -1;
|
|
if (!throttle_initialized) {
|
|
clock_gettime(CLOCK_MONOTONIC, &last_send_time);
|
|
throttle_initialized = 1;
|
|
} else {
|
|
struct timespec now;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
long elapsed_ms = (now.tv_sec - last_send_time.tv_sec) * 1000 +
|
|
(now.tv_nsec - last_send_time.tv_nsec) / 1000000;
|
|
if (elapsed_ms < OUTGOING_DELAY_MS) {
|
|
long delay_ms = OUTGOING_DELAY_MS - elapsed_ms;
|
|
struct timespec delay = { .tv_sec = delay_ms / 1000, .tv_nsec = (delay_ms % 1000) * 1000000 };
|
|
nanosleep(&delay, NULL);
|
|
}
|
|
}
|
|
int left = n;
|
|
const char *p = buf;
|
|
while (left > 0) {
|
|
int w = net_write(s, p, left);
|
|
if (w < 0) {
|
|
if (errno == EINTR || errno == EAGAIN) continue;
|
|
return -1;
|
|
}
|
|
if (w == 0) return -1;
|
|
left -= w; p += w;
|
|
}
|
|
clock_gettime(CLOCK_MONOTONIC, &last_send_time);
|
|
return n;
|
|
}
|
|
|
|
#endif
|