From 34ca6d128a81237854723bb417e00548680ca8b4 Mon Sep 17 00:00:00 2001 From: Tobias Girstmair Date: Sun, 12 May 2024 19:27:08 +0200 Subject: [PATCH] rework irc_answer to catch specific error replies and exit when encountered we still need to pass the error line to the OR_DIE_irc macro, so it's displayed on stderr (stdout is likely redirected). --- ircpipe.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/ircpipe.c b/ircpipe.c index af223f2..e959259 100644 --- a/ircpipe.c +++ b/ircpipe.c @@ -24,6 +24,7 @@ #define OR_DIE < 0 && (perror(__FILE__ ":" STR(__LINE__)), exit(1), 0) #define OR_DIE_gai(err) if (err) {fprintf(stderr, __FILE__ ":" STR(__LINE__) ": %s\n", gai_strerror(err));exit(1);} #define OR_DIE_tls(ctx) < 0 && (exit((fprintf(stderr, __FILE__ ":" STR(__LINE__) ": %s\n", tls_error(ctx)), 1)), 0) +#define OR_DIE_irc < 0 && (exit((fprintf(stderr, __FILE__ ":" STR(__LINE__) ": %s\n", "got IRC error"), 1)), 0) enum pass_type_e { NO_PASSWD, @@ -105,23 +106,47 @@ sock_t irc_connect(const char *host, const char *port, const int tls, const char } enum { /* requested command: */ - NO_CMD = 1<<0, - NICK = 1<<1, - PING = 1<<2 + NO_CMD = 0, + NICK = 1<<0, + JOIN = 1<<1, + PING = 1<<2, + ERRS = 1<<3 }; int irc_answer(const sock_t sock, char *buf, const unsigned int command) { unsigned int seen = 0; char *saveptr; char *line = strtok_r(buf, "\n", &saveptr); + /*TODO: it often happens that we take multiple calls to read() all the available lines (e.g. large motd, NAMES message). when this happens, one call to read() will return an incomplete line, and the next will start in the middle of a line. this can't be parsed properly! we need to check if the last line ends with a newline. that's hard because we use strtok which removes newlines. on the second read we should either skip over the first partial line or better, defer parsing the last line of the first read until we have the complete line.*/ do { /* skip over prefix (servername): */ if (line[0] == ':') while (*line && *line++ != ' '); - /* look for command responses, if any: */ + /* look for command responses or error numerics, if any: */ switch (command) { case PING: seen |= PING * (strncmp(line, "PONG ", 5)==0); break; - case NICK: seen |= NICK * (strncmp(line, "001 " , 4)==0); break; + case JOIN: seen |= JOIN * (strncmp(line, "JOIN ", 5)==0); + seen |= ERRS * (strncmp(line, "403 ", 4)==0); + seen |= ERRS * (strncmp(line, "405 ", 4)==0); + seen |= ERRS * (strncmp(line, "471 ", 4)==0); + seen |= ERRS * (strncmp(line, "473 ", 4)==0); + seen |= ERRS * (strncmp(line, "474 ", 4)==0); + seen |= ERRS * (strncmp(line, "475 ", 4)==0); + seen |= ERRS * (strncmp(line, "476 ", 4)==0); break; + case NICK: seen |= NICK * (strncmp(line, "001 ", 4)==0); + seen |= ERRS * (strncmp(line, "432 ", 4)==0); + seen |= ERRS * (strncmp(line, "433 ", 4)==0); + seen |= ERRS * (strncmp(line, "436 ", 4)==0); + seen |= ERRS * (strncmp(line, "464 ", 4)==0); + seen |= ERRS * (strncmp(line, "902 ", 4)==0); + seen |= ERRS * (strncmp(line, "904 ", 4)==0); break; + } + /* look for fatal error, if any */ + if (strncmp(line, "ERROR ", 6)==0) seen |= ERRS; + + if (seen & ERRS) { + /* TODO: set &buf to line to preserve error across function call */ + return seen; } /* reply to pings: */ @@ -186,19 +211,32 @@ int irc_setup(const sock_t sock, const int outfd, const char *nick, const char * WRITE(sock, "CAP END\r\n", 9); } - /* block until we get a RPL_WELCOME: */ + /* block until we get a RPL_WELCOME or an error: */ for (;;) { if (poll(fds, 1, POLL_TIMEOUT)) { n = READ(sock, buf, BUFSIZ); write(outfd, buf, n); - if (irc_answer(sock, buf, NICK) & NICK) break; - /* todo: if SERVER_PASSWD is wrong, we get error code 464 instead of NICK/001*/ + n = irc_answer(sock, buf, NICK); + if (n & NICK) break; + else if (n & ERRS) return -1; /* mostly for 433, 464 */ } } if (chan) { n = snprintf(buf, BUFSIZ, "JOIN %s\r\n", chan); WRITE(sock, buf, n); + + /* block until we get a JOIN response or an error: */ + /* todo: dedup this block with NICK/RPL_WELCOME */ + for (;;) { + if (poll(fds, 1, POLL_TIMEOUT)) { + n = READ(sock, buf, BUFSIZ); + write(outfd, buf, n); + n = irc_answer(sock, buf, JOIN); + if (n & JOIN) break; + else if (n & ERRS) return -1; /* mostly for 403, 471-475 */ + } + } } return 0; @@ -314,7 +352,7 @@ int main(int argc, char **argv) { } sock = irc_connect(host, port, tls, ca_file); sock.fd OR_DIE; - irc_setup(sock, 1, nick, pass, pass_type, chan) OR_DIE; + irc_setup(sock, 1, nick, pass, pass_type, chan) OR_DIE_irc; rv = irc_poll(sock, 0, 1); irc_cleanup(sock); -- 2.39.3