cio

a simple irc client
git clone git://kloet.net/cio
Download | Log | Files | Refs | README | LICENSE

commit 3a6e082601a65e9a451291d066abc11d6d70fb08
parent 11f1f2d8fd56694a131658e06ac5e94465ece368
Author: Andrew Kloet <andrew@kloet.net>
Date:   Sat,  2 May 2026 12:21:14 -0400

style: wrap at 78 columns

Diffstat:
Mcio.c | 133++++++++++++++++++++++++++++++++++++++++++-------------------------------------
1 file changed, 70 insertions(+), 63 deletions(-)

diff --git a/cio.c b/cio.c @@ -1,13 +1,13 @@ /* See LICENSE file for copyright and license details. * * cio is driven by a synchronous select(2) loop. It multiplexes stdin, the - * network socket, and SIGWINCH for terminal resizing. Each channel's scrollback - * is stored in an exponentially-reallocating heap buffer. + * network socket, and SIGWINCH for terminal resizing. Each channel's + * scrollback is stored in an exponentially-reallocating heap buffer. * - * Channels are organized in a fixed-size array. The '0' channel is reserved for - * server messages (status), while others are created dynamically upon JOIN or - * PRIVMSG. IRC protocol parsing is handled via a string tokenizer, dispatching - * commands through a lookup table in scmd(). + * Channels are organized in a fixed-size array. The '0' channel is reserved + * for server messages (status), while others are created dynamically upon + * JOIN or PRIVMSG. IRC protocol parsing is handled via a string tokenizer, + * dispatching commands through a lookup table in scmd(). * * UI rendering is handled by ncurses and UTF-8 is supported by ncursesw. * @@ -58,9 +58,10 @@ typedef enum { CMD_UNKNOWN, NOTICE, PRIVMSG, PING, PONG, PART, JOIN, QUIT, NICK, CAP, - AUTHENTICATE, RPL_TOPIC, RPL_TOPICWHOTIME, RPL_NAMREPLY, RPL_ENDOFNAMES, - ERR_NICKNAMEINUSE, ERR_CHANNELISFULL, ERR_INVITEONLYCHAN, - ERR_BADCHANNELKEY, ERR_NOCHANMODES, SASL_OK, SASL_ERR + AUTHENTICATE, RPL_TOPIC, RPL_TOPICWHOTIME, RPL_NAMREPLY, + RPL_ENDOFNAMES, ERR_NICKNAMEINUSE, ERR_CHANNELISFULL, + ERR_INVITEONLYCHAN, ERR_BADCHANNELKEY, ERR_NOCHANMODES, SASL_OK, + SASL_ERR } IrcCmd; struct { @@ -68,17 +69,19 @@ struct { IrcCmd id; } cmd_map[] = { { "NOTICE", NOTICE }, { "PRIVMSG", PRIVMSG }, { "PING", PING }, - { "PONG", PONG }, { "PART", PART }, { "JOIN", JOIN }, { "QUIT", QUIT }, - { "NICK", NICK}, { "CAP", CAP }, { "AUTHENTICATE", AUTHENTICATE }, - { "332", RPL_TOPIC }, { "333", RPL_TOPICWHOTIME }, - { "353", RPL_NAMREPLY }, { "366", RPL_ENDOFNAMES }, - { "477", ERR_NOCHANMODES }, { "433", ERR_NICKNAMEINUSE }, - { "471", ERR_CHANNELISFULL }, { "473", ERR_INVITEONLYCHAN }, - { "475", ERR_BADCHANNELKEY }, { "903", SASL_OK }, { "904", SASL_ERR } + { "PONG", PONG }, { "PART", PART }, { "JOIN", JOIN }, + { "QUIT", QUIT }, { "NICK", NICK}, { "CAP", CAP }, + { "AUTHENTICATE", AUTHENTICATE }, { "332", RPL_TOPIC }, + { "333", RPL_TOPICWHOTIME }, { "353", RPL_NAMREPLY }, + { "366", RPL_ENDOFNAMES }, { "477", ERR_NOCHANMODES }, + { "433", ERR_NICKNAMEINUSE }, { "471", ERR_CHANNELISFULL }, + { "473", ERR_INVITEONLYCHAN }, { "475", ERR_BADCHANNELKEY }, + { "903", SASL_OK }, { "904", SASL_ERR } }; enum { ChanLen = 64, + NickLen = 64, LineLen = 512, MaxChans = 16, MaxParams = 15, @@ -119,7 +122,7 @@ static struct { SSL *ssl; SSL_CTX *ctx; } srv; -static char nick[64]; +static char nick[NickLen]; static char cert[PATH_MAX]; static char key[512]; static int quit, winchg; @@ -177,7 +180,7 @@ srd(void) int rd, argc; if (p - l >= BufSz) - p = l; /* Input buffer overflow, current behaviour truncates */ + p = l; /* Input buffer overflow */ if (ssl) rd = SSL_read(srv.ssl, p, BufSz - (p - l)); else @@ -254,13 +257,14 @@ dial(const char *host, const char *service) return "could not load system trust store"; if (!(srv.ssl = SSL_new(srv.ctx))) return "could not create ssl object"; - if (sslverify && ( - SSL_set_hostflags(srv.ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS), + if (sslverify && + (SSL_set_hostflags(srv.ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS), !SSL_set1_host(srv.ssl, host) || !SSL_set_tlsext_host_name(srv.ssl, host))) return "could not set expected hostname"; - if (!empty(cert) && (SSL_use_certificate_chain_file(srv.ssl, cert) <= 0 - || SSL_use_PrivateKey_file(srv.ssl, cert, SSL_FILETYPE_PEM) <= 0)) + if (!empty(cert) && + (SSL_use_certificate_chain_file(srv.ssl, cert) != 1 || + SSL_use_PrivateKey_file(srv.ssl, cert, SSL_FILETYPE_PEM) != 1)) return "failed to load certificate or private key"; SSL_set_fd(srv.ssl, srv.fd); if (SSL_connect(srv.ssl) <= 0) @@ -406,9 +410,9 @@ pushf(int cn, const char *fmt, ...) n += (wrote < 0) ? 0 : wrote; /* failed to append to buffer */ va_end(vl); if (logfp) { - char isodate[32]; - strftime(isodate, sizeof(isodate), "%Y-%m-%dT%H:%M:%SZ", &gmtm); - fprintf(logfp, "%-12.12s\t%s\t%s\n", c->name, isodate, s); + char isotm[32]; + strftime(isotm, sizeof(isotm), "%Y-%m-%dT%H:%M:%SZ", &gmtm); + fprintf(logfp, "%-12.12s\t%s\t%s\n", c->name, isotm, s); fflush(logfp); } c->eol += n; @@ -450,7 +454,7 @@ scmd(char *usr, char *cmd, int argc, char **argv) else if (type == PRIVMSG) chan = usr; else chan = NULL; if (!chan) c = 0; - else if (!(c = chfind(chan)) && (c = chadd(chan, 0)) < 0) break; + else if ((c = chadd(chan, 0)) < 0) break; int high = (type == PRIVMSG && strcasestr(argv[1], nick)); pushf(c, high ? PFMTHIGH : PFMT, usr, argv[1]); if (ch != c) { @@ -476,10 +480,12 @@ scmd(char *usr, char *cmd, int argc, char **argv) pushf(0, "-!- %s is now known as %s", usr, argv[0]); break; case RPL_TOPIC: - if (argc >= 3) pushf(chfind(argv[1]), "-!- Topic: %s", argv[2]); + if (argc >= 3) + pushf(chfind(argv[1]), "-!- Topic: %s", argv[2]); break; case RPL_NAMREPLY: - if (argc >= 4) pushf(chfind(argv[2]), "-!- Names: %s", argv[3]); + if (argc >= 4) + pushf(chfind(argv[2]), "-!- Names: %s", argv[3]); break; case ERR_NOCHANMODES: /* FALLTHROUGH */ case ERR_CHANNELISFULL: @@ -748,6 +754,27 @@ tdrawinput(void) wnoutrefresh(scr.iw); } +static size_t prev_char(size_t pos) { + if (pos == 0) return 0; + size_t i = pos - 1; + while (i > 0 && IS_CONT(inp.buf[i])) i--; + return i; +} + +static size_t next_char(size_t pos) { + if (pos >= inp.len) return inp.len; + size_t i = pos + 1; + while (i < inp.len && IS_CONT(inp.buf[i])) i++; + return i; +} + +static void buf_delete(size_t start, size_t end) { + if (start >= end) return; + memmove(&inp.buf[start], &inp.buf[end], inp.len - end); + inp.len -= (end - start); + inp.cu = start; +} + static void tgetch(void) { @@ -758,14 +785,14 @@ tgetch(void) size_t i, old; switch (wc) { - case CTRL('n'): + case CTRL('n'): /* FALLTHROUGH */ case CTRL('p'): ch = (ch + (wc == CTRL('n') ? 1 : -1) + nch) % nch; chl[ch].high = chl[ch].new = 0; tdrawbar(); tredraw(); return; - case KEY_PPAGE: + case KEY_PPAGE: /* FALLTHROUGH */ case KEY_NPAGE: chl[ch].n += (wc == KEY_PPAGE) ? SCROLL : -SCROLL; if (chl[ch].n < 0) @@ -778,53 +805,33 @@ tgetch(void) case CTRL('e'): inp.cu = inp.len; break; - case CTRL('b'): + case CTRL('b'): /* FALLTHROUGH */ case KEY_LEFT: - if (inp.cu <= 0) return; - do { inp.cu--; } - while (inp.cu > 0 && IS_CONT(inp.buf[inp.cu])); + inp.cu = prev_char(inp.cu); break; - case CTRL('f'): + case CTRL('f'): /* FALLTHROUGH */ case KEY_RIGHT: - if (inp.cu >= inp.len) return; - do { inp.cu++; } - while (inp.cu < inp.len && IS_CONT(inp.buf[inp.cu])); + inp.cu = next_char(inp.cu); break; case CTRL('k'): inp.len = inp.cu; break; case CTRL('u'): - if (inp.cu == 0) return; - memmove(inp.buf, p, tail); - inp.len = tail; - inp.cu = 0; + buf_delete(0, inp.cu); break; case CTRL('d'): - if (inp.cu >= inp.len) return; - i = 1; - while (inp.cu + i < inp.len && IS_CONT(inp.buf[inp.cu + i])) - i++; - memmove(p, p + i, tail - i); - inp.len -= i; + buf_delete(inp.cu, next_char(inp.cu)); break; - case CTRL('h'): + case CTRL('h'): /* FALLTHROUGH */ case KEY_BACKSPACE: - if (inp.cu == 0) return; - old = inp.cu; - do { inp.cu--; } while (inp.cu > 0 && IS_CONT(inp.buf[inp.cu])); - memmove(&inp.buf[inp.cu], &inp.buf[old], inp.len - old); - inp.len -= (old - inp.cu); + if (inp.cu > 0) buf_delete(prev_char(inp.cu), inp.cu); break; case CTRL('w'): - if (inp.cu == 0) break; old = inp.cu; while (inp.cu > 0 && inp.buf[inp.cu - 1] == ' ') inp.cu--; - while (inp.cu > 0 && inp.buf[inp.cu - 1] != ' ') { - inp.cu--; - while (inp.cu > 0 && IS_CONT(inp.buf[inp.cu])) inp.cu--; - } - memmove(&inp.buf[inp.cu], &inp.buf[old], inp.len - old); - inp.len -= (old - inp.cu); + while (inp.cu > 0 && inp.buf[inp.cu - 1] != ' ') + inp.cu = prev_char(inp.cu); + buf_delete(inp.cu, old); break; case '\n': inp.buf[inp.len] = 0; @@ -882,7 +889,7 @@ main(int argc, char *argv[]) die("cio: fopen: logfile:"); break; case 'n': - if (strlcpy(nick, optarg, sizeof(nick)) >= sizeof(nick)) + if (strlcpy(nick, optarg, sizeof(nick)) >= NickLen) goto usage; break; case 'T': @@ -901,7 +908,7 @@ main(int argc, char *argv[]) port = optarg; break; case 'c': - if (strlcpy(cert, optarg, sizeof(cert)) >= sizeof(cert)) + if (strlcpy(cert, optarg, sizeof(cert)) >= PATH_MAX) goto usage; break; default: