diff options
-rw-r--r-- | bounce.c | 2 | ||||
-rw-r--r-- | bounce.h | 13 | ||||
-rw-r--r-- | client.c | 19 | ||||
-rw-r--r-- | server.c | 60 | ||||
-rw-r--r-- | state.c | 297 |
5 files changed, 194 insertions, 197 deletions
diff --git a/bounce.c b/bounce.c index dcf9eab..38ddf28 100644 --- a/bounce.c +++ b/bounce.c @@ -150,7 +150,7 @@ int main(int argc, char *argv[]) { size_t binds = listenBind(bind, 8, localHost, localPort); int server = serverConnect(insecure, host, port); - serverLogin(pass, auth, nick, user, real); + stateLogin(pass, auth, nick, user, real); while (!stateReady()) serverRecv(); if (join) serverFormat("JOIN :%s\r\n", join); diff --git a/bounce.h b/bounce.h index e412358..9221185 100644 --- a/bounce.h +++ b/bounce.h @@ -30,6 +30,8 @@ #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) +typedef unsigned char byte; + static const char *SourceURL = "https://code.causal.agency/june/pounce"; static const char *Origin = "irc.invalid"; @@ -68,11 +70,6 @@ size_t listenBind(int fds[], size_t cap, const char *host, const char *port); struct tls *listenAccept(int *fd, int bind); int serverConnect(bool insecure, const char *host, const char *port); -void serverLogin( - const char *pass, const char *auth, - const char *nick, const char *user, const char *real -); -void serverAuth(void); void serverRecv(void); void serverSend(const char *ptr, size_t len); void serverFormat(const char *format, ...) @@ -90,7 +87,11 @@ size_t clientDiff(const struct Client *client); void clientConsume(struct Client *client); bool stateJoinNames; +void stateLogin( + const char *pass, const char *auth, + const char *nick, const char *user, const char *real +); bool stateReady(void); void stateParse(char *line); void stateSync(struct Client *client); -const char *stateSelf(void); +const char *stateEcho(void); diff --git a/client.c b/client.c index 2b075ec..3ed8a81 100644 --- a/client.c +++ b/client.c @@ -173,17 +173,14 @@ static void handleQuit(struct Client *client, struct Message *msg) { static void handlePrivmsg(struct Client *client, struct Message *msg) { if (!msg->params[0] || !msg->params[1]) return; - // FIXME: Check against ISUPPORT CHANTYPES? - if (msg->params[0][0] == '#') { - char line[1024]; - snprintf( - line, sizeof(line), ":%s %s %s :%s", - stateSelf(), msg->cmd, msg->params[0], msg->params[1] - ); - size_t diff = ringDiff(client->consumer); - ringProduce(line); - if (!diff) ringConsume(NULL, client->consumer); - } + char line[1024]; + snprintf( + line, sizeof(line), ":%s %s %s :%s", + stateEcho(), msg->cmd, msg->params[0], msg->params[1] + ); + size_t diff = ringDiff(client->consumer); + ringProduce(line); + if (!diff) ringConsume(NULL, client->consumer); serverFormat("%s %s :%s\r\n", msg->cmd, msg->params[0], msg->params[1]); } diff --git a/server.c b/server.c index 5d985cb..d0181bb 100644 --- a/server.c +++ b/server.c @@ -29,8 +29,6 @@ #include "bounce.h" -typedef unsigned char byte; - static struct tls *client; int serverConnect(bool insecure, const char *host, const char *port) { @@ -106,64 +104,6 @@ void serverFormat(const char *format, ...) { serverSend(buf, len); } -static const char Base64[64] = { - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -}; - -static char *base64(const byte *src, size_t len) { - char *dst = malloc(1 + (len + 2) / 3 * 4); - if (!dst) err(EX_OSERR, "malloc"); - size_t i = 0; - while (len > 2) { - dst[i++] = Base64[0x3F & (src[0] >> 2)]; - dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)]; - dst[i++] = Base64[0x3F & (src[1] << 2 | src[2] >> 6)]; - dst[i++] = Base64[0x3F & src[2]]; - src += 3; - len -= 3; - } - if (len) { - dst[i++] = Base64[0x3F & (src[0] >> 2)]; - if (len > 1) { - dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)]; - dst[i++] = Base64[0x3F & (src[1] << 2)]; - } else { - dst[i++] = Base64[0x3F & (src[0] << 4)]; - dst[i++] = '='; - } - dst[i++] = '='; - } - dst[i] = '\0'; - return dst; -} - -static char *authPlain; - -void serverLogin( - const char *pass, const char *auth, - const char *nick, const char *user, const char *real -) { - if (auth) { - byte plain[1 + strlen(auth)]; - plain[0] = 0; - for (size_t i = 0; auth[i]; ++i) { - plain[1 + i] = (auth[i] == ':' ? 0 : auth[i]); - } - authPlain = base64(plain, sizeof(plain)); - serverFormat("CAP REQ :sasl\r\n"); - } - if (pass) serverFormat("PASS :%s\r\n", pass); - serverFormat("NICK %s\r\n", nick); - serverFormat("USER %s 0 * :%s\r\n", user, real); -} - -void serverAuth(void) { - assert(authPlain); - serverFormat("AUTHENTICATE %s\r\n", authPlain); - free(authPlain); - authPlain = NULL; -} - void serverRecv(void) { static char buf[4096]; static size_t len; diff --git a/state.c b/state.c index c63a6b2..85740ed 100644 --- a/state.c +++ b/state.c @@ -24,81 +24,112 @@ #include "bounce.h" -static struct { - char *origin; - char *welcome; - char *yourHost; - char *created; - char *myInfo[4]; -} intro; - -static struct { - char *nick; - char *origin; -} self; +typedef void Handler(struct Message *msg); -static void set(char **field, const char *value) { - if (*field) free(*field); - *field = strdup(value); - if (!*field) err(EX_OSERR, "strdup"); +static void require(const struct Message *msg, bool origin, size_t len) { + if (origin && !msg->origin) { + errx(EX_PROTOCOL, "%s missing origin", msg->cmd); + } + for (size_t i = 0; i < len; ++i) { + if (msg->params[i]) continue; + errx(EX_PROTOCOL, "%s missing parameter %zu", msg->cmd, 1 + i); + } } -struct Channel { - char *name; - char *topic; +static const char Base64[64] = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" }; -static struct { - struct Channel *ptr; - size_t cap, len; -} chans; - -static void chanAdd(const char *name) { - if (chans.len == chans.cap) { - chans.cap = (chans.cap ? chans.cap * 2 : 8); - chans.ptr = realloc(chans.ptr, sizeof(*chans.ptr) * chans.cap); - if (!chans.ptr) err(EX_OSERR, "realloc"); +static char *base64(const byte *src, size_t len) { + char *dst = malloc(1 + (len + 2) / 3 * 4); + if (!dst) err(EX_OSERR, "malloc"); + size_t i = 0; + while (len > 2) { + dst[i++] = Base64[0x3F & (src[0] >> 2)]; + dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)]; + dst[i++] = Base64[0x3F & (src[1] << 2 | src[2] >> 6)]; + dst[i++] = Base64[0x3F & src[2]]; + src += 3; + len -= 3; } - struct Channel *chan = &chans.ptr[chans.len++]; - chan->name = strdup(name); - if (!chan->name) err(EX_OSERR, "strdup"); - chan->topic = NULL; + if (len) { + dst[i++] = Base64[0x3F & (src[0] >> 2)]; + if (len > 1) { + dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)]; + dst[i++] = Base64[0x3F & (src[1] << 2)]; + } else { + dst[i++] = Base64[0x3F & (src[0] << 4)]; + dst[i++] = '='; + } + dst[i++] = '='; + } + dst[i] = '\0'; + return dst; } -static void chanTopic(const char *name, const char *topic) { - for (size_t i = 0; i < chans.len; ++i) { - if (strcmp(chans.ptr[i].name, name)) continue; - free(chans.ptr[i].topic); - chans.ptr[i].topic = strdup(topic); - if (!chans.ptr[i].topic) err(EX_OSERR, "strdup"); - break; +static char *plainBase64; + +void stateLogin( + const char *pass, const char *auth, + const char *nick, const char *user, const char *real +) { + if (auth) { + byte plain[1 + strlen(auth)]; + plain[0] = 0; + for (size_t i = 0; auth[i]; ++i) { + plain[1 + i] = (auth[i] == ':' ? 0 : auth[i]); + } + plainBase64 = base64(plain, sizeof(plain)); + serverFormat("CAP REQ :sasl\r\n"); } + if (pass) serverFormat("PASS :%s\r\n", pass); + serverFormat("NICK %s\r\n", nick); + serverFormat("USER %s 0 * :%s\r\n", user, real); } -static void chanRemove(const char *name) { - for (size_t i = 0; i < chans.len; ++i) { - if (strcmp(chans.ptr[i].name, name)) continue; - free(chans.ptr[i].name); - free(chans.ptr[i].topic); - chans.ptr[i] = chans.ptr[--chans.len]; - break; +static void handleCap(struct Message *msg) { + require(msg, false, 3); + if (strcmp(msg->params[1], "ACK") || strncmp(msg->params[2], "sasl", 4)) { + errx(EX_CONFIG, "server does not support SASL"); } + serverFormat("AUTHENTICATE PLAIN\r\n"); +} + +static void handleAuthenticate(struct Message *msg) { + (void)msg; + if (!plainBase64) errx(EX_PROTOCOL, "unsolicited AUTHENTICATE"); + serverFormat("AUTHENTICATE %s\r\n", plainBase64); + free(plainBase64); + plainBase64 = NULL; +} + +static void handleReplyLoggedIn(struct Message *msg) { + (void)msg; + serverFormat("CAP END\r\n"); +} + +static void handleErrorSASLFail(struct Message *msg) { + require(msg, false, 2); + errx(EX_CONFIG, "%s", msg->params[1]); } static struct { - char **tokens; - size_t cap, len; -} support; + char *nick; + char *origin; +} self; -static void supportAdd(const char *token) { - if (support.len == support.cap) { - support.cap = (support.cap ? support.cap * 2 : 8); - support.tokens = realloc(support.tokens, sizeof(char *) * support.cap); - if (!support.tokens) err(EX_OSERR, "realloc"); - } - support.tokens[support.len] = strdup(token); - if (!support.tokens[support.len]) err(EX_OSERR, "strdup"); - support.len++; +static struct { + char *origin; + char *welcome; + char *yourHost; + char *created; + char *myInfo[4]; +} intro; + +const char *stateEcho(void) { + if (self.origin) return self.origin; + if (self.nick) return self.nick; + return "*"; } bool stateReady(void) { @@ -110,125 +141,155 @@ bool stateReady(void) { && intro.myInfo[0]; } -const char *stateSelf(void) { - if (self.origin) return self.origin; - if (self.nick) return self.nick; - return "*"; -} - -typedef void Handler(struct Message *msg); - -static void handleCap(struct Message *msg) { - bool ack = msg->params[1] && !strcmp(msg->params[1], "ACK"); - bool sasl = msg->params[2] && !strncmp(msg->params[2], "sasl", 4); - if (!ack || !sasl) errx(EX_CONFIG, "server does not support SASL"); - serverFormat("AUTHENTICATE PLAIN\r\n"); -} - -static void handleAuthenticate(struct Message *msg) { - (void)msg; - serverAuth(); -} - -static void handleReplyLoggedIn(struct Message *msg) { - (void)msg; - serverFormat("CAP END\r\n"); +static void set(char **field, const char *value) { + if (*field) free(*field); + *field = strdup(value); + if (!*field) err(EX_OSERR, "strdup"); } -static void handleErrorSASLFail(struct Message *msg) { - if (!msg->params[1]) errx(EX_PROTOCOL, "RPL_SASLFAIL without message"); - errx(EX_CONFIG, "%s", msg->params[1]); +static void handleErrorNicknameInUse(struct Message *msg) { + if (self.nick) return; + require(msg, false, 2); + serverFormat("NICK %s_\r\n", msg->params[1]); } static void handleReplyWelcome(struct Message *msg) { - if (!msg->params[1]) errx(EX_PROTOCOL, "RPL_WELCOME without message"); + require(msg, true, 2); set(&intro.origin, msg->origin); set(&self.nick, msg->params[0]); set(&intro.welcome, msg->params[1]); } static void handleReplyYourHost(struct Message *msg) { - if (!msg->params[1]) errx(EX_PROTOCOL, "RPL_YOURHOST without message"); + require(msg, false, 2); set(&intro.yourHost, msg->params[1]); } static void handleReplyCreated(struct Message *msg) { - if (!msg->params[1]) errx(EX_PROTOCOL, "RPL_CREATED without message"); + require(msg, false, 2); set(&intro.created, msg->params[1]); } static void handleReplyMyInfo(struct Message *msg) { - if (!msg->params[4]) errx(EX_PROTOCOL, "RPL_MYINFO without 4 parameters"); + require(msg, false, 5); set(&intro.myInfo[0], msg->params[1]); set(&intro.myInfo[1], msg->params[2]); set(&intro.myInfo[2], msg->params[3]); set(&intro.myInfo[3], msg->params[4]); } +static struct { + char **tokens; + size_t cap, len; +} support; + +static void supportAdd(const char *token) { + if (support.len == support.cap) { + support.cap = (support.cap ? support.cap * 2 : 8); + support.tokens = realloc(support.tokens, sizeof(char *) * support.cap); + if (!support.tokens) err(EX_OSERR, "realloc"); + } + support.tokens[support.len] = strdup(token); + if (!support.tokens[support.len]) err(EX_OSERR, "strdup"); + support.len++; +} + static void handleReplyISupport(struct Message *msg) { + require(msg, false, 1); for (size_t i = 1; i < ParamCap; ++i) { if (!msg->params[i] || strchr(msg->params[i], ' ')) break; supportAdd(msg->params[i]); } } -static void handleErrorNicknameInUse(struct Message *msg) { - if (self.nick) return; - if (!msg->params[1]) errx(EX_PROTOCOL, "ERR_NICKNAMEINUSE without nick"); - serverFormat("NICK %s_\r\n", msg->params[1]); +struct Channel { + char *name; + char *topic; +}; + +static struct { + struct Channel *ptr; + size_t cap, len; +} chans; + +static void chanAdd(const char *name) { + if (chans.len == chans.cap) { + chans.cap = (chans.cap ? chans.cap * 2 : 8); + chans.ptr = realloc(chans.ptr, sizeof(*chans.ptr) * chans.cap); + if (!chans.ptr) err(EX_OSERR, "realloc"); + } + struct Channel *chan = &chans.ptr[chans.len++]; + chan->name = strdup(name); + if (!chan->name) err(EX_OSERR, "strdup"); + chan->topic = NULL; +} + +static void chanTopic(const char *name, const char *topic) { + for (size_t i = 0; i < chans.len; ++i) { + if (strcmp(chans.ptr[i].name, name)) continue; + set(&chans.ptr[i].topic, topic); + break; + } +} + +static void chanRemove(const char *name) { + for (size_t i = 0; i < chans.len; ++i) { + if (strcmp(chans.ptr[i].name, name)) continue; + free(chans.ptr[i].name); + free(chans.ptr[i].topic); + chans.ptr[i] = chans.ptr[--chans.len]; + break; + } } -static bool fromSelf(const struct Message *msg) { +static bool originSelf(const char *origin) { if (!self.nick) return false; + size_t len = strlen(self.nick); - if (strlen(msg->origin) < len) return false; - if (strncmp(msg->origin, self.nick, len)) return false; - if (msg->origin[len] != '!') return false; - if (!self.origin || strcmp(self.origin, msg->origin)) { - set(&self.origin, msg->origin); + if (strlen(origin) < len) return false; + if (strncmp(origin, self.nick, len)) return false; + if (origin[len] != '!') return false; + + if (!self.origin || strcmp(self.origin, origin)) { + set(&self.origin, origin); } return true; } static void handleNick(struct Message *msg) { - if (!msg->origin) errx(EX_PROTOCOL, "NICK without origin"); - if (!msg->params[0]) errx(EX_PROTOCOL, "NICK without nick"); - if (fromSelf(msg)) set(&self.nick, msg->params[0]); + require(msg, true, 1); + if (originSelf(msg->origin)) set(&self.nick, msg->params[0]); } static void handleJoin(struct Message *msg) { - if (!msg->origin) errx(EX_PROTOCOL, "JOIN without origin"); - if (!msg->params[0]) errx(EX_PROTOCOL, "JOIN without channel"); - if (fromSelf(msg)) chanAdd(msg->params[0]); + require(msg, true, 1); + if (originSelf(msg->origin)) chanAdd(msg->params[0]); } static void handlePart(struct Message *msg) { - if (!msg->origin) errx(EX_PROTOCOL, "PART without origin"); - if (!msg->params[0]) errx(EX_PROTOCOL, "PART without channel"); - if (fromSelf(msg)) chanRemove(msg->params[0]); + require(msg, true, 1); + if (originSelf(msg->origin)) chanRemove(msg->params[0]); } static void handleKick(struct Message *msg) { - if (!msg->params[0]) errx(EX_PROTOCOL, "KICK without channel"); - if (!msg->params[1]) errx(EX_PROTOCOL, "KICK without nick"); + require(msg, false, 2); if (self.nick && !strcmp(msg->params[1], self.nick)) { chanRemove(msg->params[0]); } } static void handleTopic(struct Message *msg) { - if (!msg->params[0]) errx(EX_PROTOCOL, "TOPIC without channel"); - if (!msg->params[1]) errx(EX_PROTOCOL, "TOPIC without topic"); + require(msg, false, 2); chanTopic(msg->params[0], msg->params[1]); } static void handleReplyTopic(struct Message *msg) { - if (!msg->params[1]) errx(EX_PROTOCOL, "RPL_TOPIC without channel"); - if (!msg->params[2]) errx(EX_PROTOCOL, "RPL_TOPIC without topic"); + require(msg, false, 3); chanTopic(msg->params[1], msg->params[2]); } static void handleError(struct Message *msg) { + require(msg, false, 1); errx(EX_UNAVAILABLE, "%s", msg->params[0]); } @@ -281,13 +342,11 @@ void stateSync(struct Client *client) { client, ":%s 001 %s :%s\r\n" ":%s 002 %s :%s\r\n" - ":%s 003 %s :%s\r\n", + ":%s 003 %s :%s\r\n" + ":%s 004 %s %s %s %s %s\r\n", intro.origin, self.nick, intro.welcome, intro.origin, self.nick, intro.yourHost, - intro.origin, self.nick, intro.created - ); - clientFormat( - client, ":%s 004 %s %s %s %s %s\r\n", + intro.origin, self.nick, intro.created, intro.origin, self.nick, intro.myInfo[0], intro.myInfo[1], intro.myInfo[2], intro.myInfo[3] ); |