From 07a1946a7fcaf967467084f902da4c28ea9907b0 Mon Sep 17 00:00:00 2001 From: Egor Date: Wed, 24 Jan 2024 09:15:51 +0200 Subject: [PATCH] add SASL auth support --- src/common.h | 2 + src/irc.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/main.c | 8 ++- 3 files changed, 142 insertions(+), 6 deletions(-) diff --git a/src/common.h b/src/common.h index 1c59cba..33029c3 100644 --- a/src/common.h +++ b/src/common.h @@ -48,6 +48,8 @@ struct client_data { char *pass_reg; char *peername; unsigned short peerport; + char *saslbuf; + size_t saslbufLen; char buf[2048]; size_t bufLen; char wbuf[8192]; diff --git a/src/irc.c b/src/irc.c index fb28f67..511f953 100644 --- a/src/irc.c +++ b/src/irc.c @@ -204,6 +204,7 @@ int processPacket(struct client_data *client, char *source, char *command, int a { "chghost", CAP_CHGHOST }, { "setname", CAP_SETNAME }, { "account-notify", CAP_ACCOUNTNOTIFY }, + { "sasl", 0 }, { 0, 0 } }; @@ -213,7 +214,9 @@ int processPacket(struct client_data *client, char *source, char *command, int a if (strcmp(args[0], "LS") == 0) { if (args[1] && atoi(args[1]) >= 302) client->caps |= CAP_302; - copy_strf(&databuf, ":%s CAP * LS :cap-notify multi-prefix extended-join away-notify invite-notify extended-monitor chghost setname account-notify\r\n", hostname); + copy_strf(&databuf, ":%s CAP * LS :cap-notify multi-prefix extended-join away-notify invite-notify extended-monitor chghost setname account-notify%s\r\n", + hostname, + client->umode & UMODE_SECURE ? " sasl" : ""); sendbuf(client); if (!is_registered(client)) { client->regstate = REG_CAPLS; @@ -263,6 +266,123 @@ int processPacket(struct client_data *client, char *source, char *command, int a // FIXME: error 410 goto unknown; } + } else if(strcmp(command, "AUTHENTICATE") == 0) { + if (is_registered(client)) + return err_alreadyregistered(client), sendbuf(client), 0; + if (!args[0]) + return err_needmoreparams(client, command), sendbuf(client), 0; + if (client->username && client->username[0] != '~') + return copy_strf(&databuf, ":%s 907 * :You have already authenticated using SASL\r\n", hostname), sendbuf(client), 0; + if (!(client->umode & UMODE_SECURE)) + return copy_strf(&databuf, ":%s 904 * :SASL authentication failed\r\n", hostname), sendbuf(client), 0; + if (!client->saslbuf) { + if (strcmp(args[0], "PLAIN") != 0) + return copy_strf(&databuf, ":%s 908 * PLAIN :are available SASL mechanisms\r\n", hostname), sendbuf(client), 0; + copy_strf(&databuf, ":%s AUTHENTICATE +\r\n", hostname), sendbuf(client); + // RFC 4616 spec says the three parts of the response can be up to 255 bytes long + // That's 255*3 + 2, but we'll add another NUL byte after the pass, so it's 768. + // Base64 encoding adds 4/3 overhead, so the total buffer size is 1024. + client->saslbuf = malloc(1024); + client->saslbufLen = 0; + if (!client->saslbuf) + abort(); + } else { + size_t len = strlen(args[0]); + // there are three cases + // len == 400 - copy the data, keep reading + // len < 400 - copy the data, finish + // data is + - don't copy, finish + if (len == 1 && args[0][0] == '*') { + copy_strf(&databuf, ":%s 906 * :SASL auhtentication aborted\r\n", hostname), sendbuf(client); + goto saslcleanup; + } + if (len != 1 || args[0][0] != '+') { + if (len > 400 || client->saslbufLen + len > 1024) { + memset(args[0], 0, len); + if (len > 400) + copy_strf(&databuf, ":%s 905 * :SASL message too long\r\n", hostname), sendbuf(client); + else + copy_strf(&databuf, ":%s 904 * :SASL authentication failed\r\n", hostname), sendbuf(client); + goto saslcleanup; + } + memcpy(client->saslbuf + client->saslbufLen, args[0], len); + client->saslbufLen += len; + memset(args[0], 0, len); + } + if (len != 400) { + while (client->saslbufLen & 3) + client->saslbuf[client->saslbufLen++] = '='; + if (client->saslbufLen == 0) + goto saslerr; + int parity = 0; + if (client->saslbuf[client->saslbufLen-1] == '=') { + client->saslbuf[client->saslbufLen-1] = 'A'; + parity = 1; + if (client->saslbuf[client->saslbufLen-2] == '=') { + client->saslbuf[client->saslbufLen-2] = 'A'; + parity = 2; + } + } + uint8_t *p, *q; + p = (uint8_t *)client->saslbuf; + for (size_t i = 0; i < client->saslbufLen; i++) { + if (*p >= 'A' && *p <= 'Z') { + *p += 0-'A'; + } else if (*p >= 'a' && *p <= 'z') { + *p += 26-'a'; + } else if (*p >= '0' && *p <= '9') { + *p += 52-'0'; + } else if (*p == '+') { + *p = 62; + } else if (*p == '/') { + *p = 63; + } else { + goto saslerr; + } + p++; + } + p = q = (uint8_t *)client->saslbuf; + for (size_t i = 0; i < client->saslbufLen; i += 4) { + *q++ = p[0]<<2 | p[1]>>4; + *q++ = p[1]<<4 | p[2]>>2; + *q++ = p[2]<<6 | p[3]; + p += 4; + } + client->saslbufLen = q-(uint8_t *)client->saslbuf - parity; + client->saslbuf[client->saslbufLen] = '\0'; + char *username = strchr(client->saslbuf, '\0')+1; + if (username > client->saslbuf + client->saslbufLen) + goto saslerr; + char *password = strchr(username, '\0')+1; + if (password > client->saslbuf + client->saslbufLen) + goto saslerr; + printf("hello %d %s:%s\n", parity, username, password); + int res = password_check(username, password, getdatafile("users.txt")); + // we'll clear the whole buffer later, this is just in case username alloc fails + memset(password, 0, strlen(password)); + if (res != -1) { + client->umode |= UMODE_REGISTERED; + free(client->username); + client->username = mystrdup(username); + if (!client->username) + abort(); + copy_strf(&databuf, ":%s 903 * :SASL authentication successful\r\n", hostname), sendbuf(client); + copy_strf(&databuf, ":%s 900 * *!%s@%s %s :You are now logged in as %s\r\n", + hostname, client->username, client->hostname, client->username, client->username), sendbuf(client); + // TODO: send 900 in newUser after we know the nuh + } else { +saslerr: + copy_strf(&databuf, ":%s 904 * :SASL authentication failed\r\n", hostname), sendbuf(client); + } +saslcleanup: + memset(client->saslbuf, 0, 1024); + free(client->saslbuf); + client->saslbuf = 0; + client->saslbufLen = 0; + newUser(client); + return 0; + } + } } else if(strcmp(command, "NICK") == 0) { // User now registered if (!args[0]) @@ -295,9 +415,6 @@ int processPacket(struct client_data *client, char *source, char *command, int a client->realname = mystrdup(args[3]); if (!client->realname) abort(); - client->hostname = mystrdup(client->peername && !strcmp(client->peername, "127.0.0.1") ? "localhost" : hostname); - if (!client->hostname) - abort(); // Send welcome newUser(client); } else if(strcmp(command, "PASS") == 0) { @@ -1190,7 +1307,7 @@ unknown: } void newUser(struct client_data *client) { - if (client->regstate == REG_CAPLS || !client->nick_reg || !client->username) + if (client->regstate == REG_CAPLS || client->saslbuf || !client->nick_reg || !client->username) return; if (client->username[0] == '~' && client->pass_reg) { int res = password_check(client->username + 1, client->pass_reg, getdatafile("users.txt")); @@ -1366,6 +1483,7 @@ void client_chghost(struct client_data *client, const char *newusername, const c sendbuf(client); rpl_visiblehost(client), sendbuf(client); int changedacc = client->username[0] != '~' || newusername[0] != '~'; + int loggedout = client->username[0] != '~' && newusername[0] == '~'; free(client->username); client->username = mystrdup(newusername); @@ -1400,6 +1518,16 @@ void client_chghost(struct client_data *client, const char *newusername, const c if (client->cookie != cookie) sendbuf(client); } + if (newusername[0] != '~') { + copy_strf(&databuf, ":%s 900 %s %s %s :You are now logged in as %s\r\n", + hostname, client->nick, client->nuh, client->username, client->username), sendbuf(client); + client->umode |= UMODE_REGISTERED; + } else { + if (loggedout) + copy_strf(&databuf, ":%s 901 %s %s :You are now logged out\r\n", + hostname, client->nick, client->nuh), sendbuf(client); + client->umode &= ~UMODE_REGISTERED; + } } void channel_sendtopic(struct client_data *client, struct channel *chan, int joining) { diff --git a/src/main.c b/src/main.c index efda7b0..a7d39ea 100644 --- a/src/main.c +++ b/src/main.c @@ -183,9 +183,12 @@ void accept_from_sock(struct listener *listener) cl->peerport = port; if (cctx || !strcmp(cl->peername, "127.0.0.1")) cl->umode |= UMODE_SECURE; + extern const char *hostname; + cl->hostname = mystrdup(!strcmp(cl->peername, "127.0.0.1") ? "localhost" : hostname); + if (!cl->hostname) + abort(); net_poll_add(cl->FD, SOCKTYPE_CLIENT, cl); update_poll_reg(cl); - extern const char *hostname; copy_strf(&databuf, ":%s NOTICE * :Hello welcome to PatchyIRCD!\r\n", hostname); sendbuf(cl); cl->next = clients; @@ -277,6 +280,9 @@ int main(int argc, char *argv[]) struct client_data *fake = allocClient(); fake->FD = INVALID_SOCKET; fake->umode |= UMODE_OPER | UMODE_SECURE; + fake->hostname = mystrdup("localhost"); + if (!fake->hostname) + abort(); strcpy_lit(fake->buf, "NICK :root\r\n"); fake->bufLen = strlen(fake->buf); parsePacket(fake); -- 2.56.0