cio

a simple irc client
Download | Log | Files | Refs | README | LICENSE

commit f3706757da84fac7c42758bc3bbe94720df82440
parent 585fc6d2e6a63db37043f079676973b34fee1175
Author: Andrew Kloet <andrew@kloet.net>
Date:   Tue, 28 Apr 2026 15:12:58 -0400

style and simplification

Hard limit to 80 columns. The only featureful change is I removed "dirty
marking" cells in the input box. It added a fair amount of complexity
for what I consider to be a very negligible amount of "wasted" cycles.

Diffstat:
Mcio.c | 280++++++++++++++++++++++++++++++++++++++-----------------------------------------
1 file changed, 136 insertions(+), 144 deletions(-)

diff --git a/cio.c b/cio.c @@ -16,46 +16,47 @@ */ #include <assert.h> #include <ctype.h> +#include <errno.h> #include <limits.h> +#include <locale.h> #include <signal.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> -#include <stdarg.h> #include <string.h> #include <time.h> -#include <errno.h> -#include <curses.h> -#include <unistd.h> #include <arpa/inet.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <sys/select.h> -#include <sys/ioctl.h> +#include <netdb.h> #include <netinet/in.h> #include <netinet/tcp.h> -#include <netdb.h> -#include <locale.h> +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <curses.h> #include <openssl/ssl.h> #include <openssl/x509v3.h> -#undef CTRL -#define CTRL(x) (x & 037) -#define GET_ARG(i) ((argc > (i)) ? argv[i] : "") +#undef CTRL +#define CTRL(x) (x & 037) +#define GET_ARG(i) ((argc > (i)) ? argv[i] : "") -#define SCROLL 15 -#define INDENT 23 -#define DATEFMT "%H:%M" -#define PFMT " %-12s < %s" -#define PFMTHIGH "> %-12s < %s" -#define SRV "irc.oftc.net" -#define PORT "6697" +#define SCROLL 15 +#define INDENT 23 +#define DATEFMT "%H:%M" +#define PFMT " %-12s < %s" +#define PFMTHIGH "> %-12s < %s" +#define SRV "irc.oftc.net" +#define PORT "6697" 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 + ERR_NICKNAMEINUSE, ERR_CHANNELISFULL, ERR_INVITEONLYCHAN, + ERR_BADCHANNELKEY, ERR_NOCHANMODES, SASL_OK, SASL_ERR } IrcCmd; struct { @@ -65,11 +66,11 @@ struct { { "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 } + { "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 { @@ -130,7 +131,7 @@ static const Rune utfmax[UtfSz + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; static void scmd(char *, char *, int, char **); static void tdrawbar(void); -static void tdrawinput(size_t); +static void tdrawinput(void); static void tredraw(void); static void treset(void); @@ -201,18 +202,20 @@ static char * b64_enc(const unsigned char *in, size_t len) { static char out[BufSz]; - const char *tab = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const char *t = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; char *p = out; size_t i; for (i = 0; i < len; i += 3) { - unsigned int v = - in[i] << 16 | (i+1 < len ? in[i+1] << 8 : 0) | (i+2 < len ? in[i+2] : 0); - *p++ = tab[(v >> 18) & 0x3F]; - *p++ = tab[(v >> 12) & 0x3F]; - *p++ = (i+1 < len) ? tab[(v >> 6) & 0x3F] : '='; - *p++ = (i+2 < len) ? tab[v & 0x3F] : '='; + unsigned int b0 = in[i]; + unsigned int b1 = (i + 1 < len) ? in[i + 1] : 0; + unsigned int b2 = (i + 2 < len) ? in[i + 2] : 0; + unsigned int v = (b0 << 16) | (b1 << 8) | b2; + *p++ = t[(v >> 18) & 0x3F]; + *p++ = t[(v >> 12) & 0x3F]; + *p++ = (i + 1 < len) ? t[(v >> 6) & 0x3F] : '='; + *p++ = (i + 2 < len) ? t[v & 0x3F] : '='; } *p = '\0'; return out; @@ -242,7 +245,7 @@ srd(void) int rd, argc; if (p - l >= BufSz) - p = l; /* Input buffer overflow, there should something better to do. */ + p = l; /* Input buffer overflow, current behaviour truncates */ if (ssl) rd = SSL_read(srv.ssl, p, BufSz - (p - l)); else @@ -294,13 +297,13 @@ sinit(const char *nick, const char *user) static char * dial(const char *host, const char *service) { - struct addrinfo hints, *res = NULL, *rp; + struct addrinfo *res, *rp, hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_NUMERICSERV + }; int fd = -1; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */ - hints.ai_flags = AI_NUMERICSERV; /* avoid name lookup for port */ - hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(host, service, &hints, &res)) return "getaddrinfo failed"; for (rp = res; rp; rp = rp->ai_next) { @@ -315,40 +318,28 @@ dial(const char *host, const char *service) freeaddrinfo(res); if ((srv.fd = fd) == -1) return "cannot connect to host"; - if (ssl) { - SSL_load_error_strings(); - SSL_library_init(); - srv.ctx = SSL_CTX_new(TLS_client_method()); - if (!srv.ctx) return "could not initialize ssl context"; - if (sslverify) { - SSL_CTX_set_verify(srv.ctx, SSL_VERIFY_PEER, NULL); - if (SSL_CTX_set_default_verify_paths(srv.ctx) != 1) - return "could not load default system trust store"; - } - srv.ssl = SSL_new(srv.ctx); - if (!srv.ssl) return "could not create SSL object"; - if (sslverify) { - SSL_set_hostflags(srv.ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); - if (!SSL_set1_host(srv.ssl, host)) - return "could not set expected hostname"; - SSL_set_tlsext_host_name(srv.ssl, host); - } else { - SSL_CTX_set_verify(srv.ctx, SSL_VERIFY_NONE, NULL); - } - if (!empty(cert)) { - if (SSL_use_certificate_chain_file(srv.ssl, cert) <= 0) - return "failed to load certificate chain"; - if (SSL_use_PrivateKey_file(srv.ssl, cert, SSL_FILETYPE_PEM) <= 0) - return "failed to load private key"; - } - SSL_set_fd(srv.ssl, srv.fd); - if (SSL_connect(srv.ssl) <= 0) { - long verify_err = SSL_get_verify_result(srv.ssl); - if (verify_err != X509_V_OK) - return (char *)X509_verify_cert_error_string(verify_err); - return "SSL handshake failed"; - } - } + if (!ssl) + return 0; + SSL_load_error_strings(); + SSL_library_init(); + srv.ctx = SSL_CTX_new(TLS_client_method()); + if (!srv.ctx) return "could not initialize ssl context"; + if (sslverify && (SSL_CTX_set_verify(srv.ctx, SSL_VERIFY_PEER, NULL), + (SSL_CTX_set_default_verify_paths(srv.ctx) != 1))) + 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), + !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)) + return "failed to load certificate or private key"; + SSL_set_fd(srv.ssl, srv.fd); + if (SSL_connect(srv.ssl) <= 0) + return "ssl handshake failed"; return 0; } @@ -405,10 +396,12 @@ chdel(const char *name) if (n <= 0) return 0; free(chl[n].buf); - if (n < nch - 1) - memmove(&chl[n], &chl[n + 1], (nch - n - 1) * sizeof(struct Chan)); + if (n < nch - 1) { /* Move trailing channels left by one */ + size_t len = (nch - n - 1) * sizeof(struct Chan); + memmove(&chl[n], &chl[n + 1], len); + } nch--; - if (ch > n || ch >= nch) + if (ch > n || ch >= nch) /* Adjust current channel focus */ ch = (ch > 0) ? ch - 1 : 0; tdrawbar(); return 1; @@ -436,7 +429,7 @@ pushl(char *p, char *e) w++; x += p - w; } - if (p >= e || *p == ' ' || p - w + INDENT >= (ptrdiff_t)scr.x - 1) { + if (p >= e || *p == ' ' || p-w+INDENT >= (ptrdiff_t)scr.x-1) { while (w < p) { w += utf8decode(w, u, UtfSz); if (wcwidth(*u) > 0 || *u == '\n') { @@ -588,12 +581,13 @@ scmd(char *usr, char *cmd, int argc, char **argv) break; case CAP: if (argc < 2) break; + int want = !empty(key) || !empty(cert); + int has = argc > 2 && strstr(argv[2], "sasl"); if (!strcmp(argv[1], "LS")) - sndf((!empty(key) || !empty(cert)) && argc > 2 && strstr(argv[2], "sasl") - ? "CAP REQ :sasl" - : "CAP END"); - else if (!strcmp(argv[1], "ACK") && argc > 2 && strstr(argv[2], "sasl")) - sndf(empty(cert) ? "AUTHENTICATE PLAIN" : "AUTHENTICATE EXTERNAL"); + sndf((want && has) ? "CAP REQ :sasl" : "CAP END"); + else if (!strcmp(argv[1], "ACK") && has) + sndf("AUTHENTICATE %s", + empty(cert) ? "PLAIN" : "EXTERNAL"); else if (!strcmp(argv[1], "NAK") || !strcmp(argv[1], "ACK")) sndf("CAP END"); break; @@ -603,24 +597,25 @@ scmd(char *usr, char *cmd, int argc, char **argv) sndf("AUTHENTICATE +"); } else if (!empty(usr) && !empty(key)) { unsigned char raw[512]; - size_t u_len = strlen(usr); - size_t k_len = strlen(key); + size_t ulen = strlen(usr); + size_t klen = strlen(key); raw[0] = '\0'; - memcpy(raw + 1, usr, u_len); - raw[1 + u_len] = '\0'; - memcpy(raw + 2 + u_len, key, k_len); - sndf("AUTHENTICATE %s", b64_enc(raw, u_len + k_len + 2)); + memcpy(raw + 1, usr, ulen); + raw[1 + ulen] = '\0'; + memcpy(raw + 2 + ulen, key, klen); + sndf("AUTHENTICATE %s", b64_enc(raw, ulen + klen + 2)); } break; case SASL_OK: case SASL_ERR: sndf("CAP END"); - pushf(0, "-!- SASL auth %s", (type == SASL_OK ? "successful" : "failed")); + pushf(0, "-!- SASL auth %s", + (type == SASL_OK ? "successful" : "failed")); break; default: { if (!isdigit(cmd[0])) break; char arg_buf[LineLen] = ""; - /* Numerics usually have the client nick at argv[0], so we skip it. */ + /* Numerics usually have the client nick at argv[0] */ for (int i = 1; i < argc; i++) { strlcat(arg_buf, argv[i], sizeof(arg_buf)); if (i < argc - 1) @@ -649,11 +644,11 @@ uparse(char *m) switch (*p) { case 'j': /* Join channels. */ p += 1 + (p[1] == ' '); - for (char *token = strtok(p, " "); token; token = strtok(NULL, " ")) { - if (chadd(token, 1) < 0) + for (char *t = strtok(p, " "); t; t = strtok(NULL, " ")) { + if (chadd(t, 1) < 0) break; - if (strchr("&#!+", token[0])) - sndf("JOIN %s", token); + if (strchr("&#!+", t[0])) + sndf("JOIN %s", t); } tredraw(); return; @@ -665,9 +660,9 @@ uparse(char *m) strlcpy(buf, chl[ch].name, sizeof(buf)); p = buf; } - for (char *token = strtok(p, " "); token; token = strtok(NULL, " ")) - if (chdel(token) && strchr("&#!+", token[0])) - sndf("PART %s", token); + for (char *t = strtok(p, " "); t; t = strtok(NULL, " ")) + if (chdel(t) && strchr("&#!+", t[0])) + sndf("PART %s", t); tredraw(); return; case 'm': /* Private message. */ @@ -738,7 +733,7 @@ tresize(void) mvwin(scr.iw, scr.y - 1, 0); tredraw(); tdrawbar(); - tdrawinput(0); + tdrawinput(); doupdate(); } @@ -810,25 +805,20 @@ tdrawbar(void) } static void -tdrawinput(size_t dirty) +tdrawinput(void) { - size_t i; + int hw = scr.x / 2; while (inp.cu < inp.shft) - dirty = 0, inp.shft -= inp.shft >= scr.x / 2 ? scr.x / 2 : inp.shft; - while (inp.cu >= scr.x + inp.shft) - dirty = 0, inp.shft += scr.x / 2; - if (dirty <= inp.shft) - i = inp.shft; - else if (dirty > scr.x + inp.shft || dirty > inp.len) - goto mvcur; - else - i = dirty; - wmove(scr.iw, 0, i - inp.shft); - wclrtoeol(scr.iw); - for (; i - inp.shft < scr.x && i < inp.len; i++) + inp.shft -= (inp.shft > hw) ? hw : inp.shft; + while (inp.cu >= inp.shft + scr.x) + inp.shft += hw; + + wmove(scr.iw, 0, 0); + for (size_t i = inp.shft; i < inp.len && i < inp.shft + scr.x; i++) waddch(scr.iw, inp.buf[i]); -mvcur: + + wclrtoeol(scr.iw); wmove(scr.iw, 0, inp.cu - inp.shft); wnoutrefresh(scr.iw); } @@ -836,10 +826,11 @@ mvcur: static void tgetch(void) { - size_t dirty = inp.len + 1, i; - int c; + char *p = &inp.buf[inp.cu]; /* Current cursor position */ + size_t tail = inp.len - inp.cu; /* Count of chars after cursor */ + size_t i; + int c = wgetch(scr.iw); - c = wgetch(scr.iw); switch (c) { case CTRL('n'): case CTRL('p'): { @@ -849,7 +840,7 @@ tgetch(void) tdrawbar(); tredraw(); return; - } + } case KEY_PPAGE: case KEY_NPAGE: chl[ch].n += (c == KEY_PPAGE) ? SCROLL : -SCROLL; @@ -874,53 +865,48 @@ tgetch(void) inp.cu++; break; case CTRL('k'): - dirty = inp.len = inp.cu; + inp.len = inp.cu; break; case CTRL('u'): if (inp.cu == 0) return; - inp.len -= inp.cu; - memmove(inp.buf, &inp.buf[inp.cu], inp.len); - dirty = inp.cu = 0; + memmove(inp.buf, p, tail); /* Move the tail to the beginning */ + inp.len = tail; + inp.cu = 0; break; case CTRL('d'): if (inp.cu >= inp.len) return; - memmove(&inp.buf[inp.cu], &inp.buf[inp.cu + 1], inp.len - inp.cu - 1); - dirty = inp.cu; + memmove(p, p + 1, tail - 1); /* Shift tail left by 1 at p */ inp.len--; break; case CTRL('h'): case KEY_BACKSPACE: if (inp.cu == 0) return; - memmove(&inp.buf[inp.cu - 1], &inp.buf[inp.cu], inp.len - inp.cu); - dirty = --inp.cu; - inp.len--; + memmove(p - 1, p, tail); /* Shift tail left by 1 at p-1 */ + inp.cu--, inp.len--; break; case CTRL('w'): if (inp.cu == 0) break; i = 1; - while (inp.buf[inp.cu - i] == ' ' && inp.cu - i != 0) i++; - while (inp.buf[inp.cu - i] != ' ' && inp.cu - i != 0) i++; - if (inp.cu - i != 0) i--; - memmove(&inp.buf[inp.cu - i], &inp.buf[inp.cu], inp.len - inp.cu); - inp.cu -= i; - dirty = inp.cu; - inp.len -= i; + /* Find the start of the word (skipping trailing spaces) */ + while (i < inp.cu && inp.buf[inp.cu - i] == ' ') i++; + while (i < inp.cu && inp.buf[inp.cu - (i + 1)] != ' ') i++; + memmove(p - i, p, tail); /* Shift tail left by 'i' positions */ + inp.cu -= i, inp.len -= i; break; case '\n': inp.buf[inp.len] = 0; uparse(inp.buf); - dirty = inp.cu = inp.len = 0; + inp.cu = inp.len = 0; break; default: if (c > CHAR_MAX || inp.len >= BufSz - 1) return; - memmove(&inp.buf[inp.cu + 1], &inp.buf[inp.cu], inp.len - inp.cu); - dirty = inp.cu; - inp.len++; + memmove(p + 1, p, tail); inp.buf[inp.cu++] = c; + inp.len++; break; } - tdrawinput(dirty); + tdrawinput(); } static void @@ -948,8 +934,10 @@ main(int argc, char *argv[]) switch (o) { case 'h': usage: - die("usage: %s [-n NICK] [-u USER] [-s SERVER] [-p PORT]\n" - " [-l LOGFILE] [-c certificate] [-hvTV]", argv[0]); + die("usage: cio [-vTV] [-n nick] [-u user]" + " [-s server]\n" + " [-p port] [-l logfile]" + " [-c certificate]"); break; case 'v': die("cio-" VERSION); @@ -1024,7 +1012,11 @@ main(int argc, char *argv[]) } if (FD_ISSET(srv.fd, &wfs)) { size_t len = outp - outb; - int wr = ssl ? SSL_write(srv.ssl, outb, len) : write(srv.fd, outb, len); + int wr; + if (ssl) + wr = SSL_write(srv.ssl, outb, len); + else + wr = write(srv.fd, outb, len); if (wr <= 0) { die("cio: write failed: connection lost"); }