]> PatchyGit - patchyircd.git/commitdiff
add SASL auth support
authorEgor <egor@patchysicp.xyz>
Wed, 24 Jan 2024 07:15:51 +0000 (09:15 +0200)
committerEgor <egor@patchysicp.xyz>
Wed, 24 Jan 2024 07:15:51 +0000 (09:15 +0200)
src/common.h
src/irc.c
src/main.c

index 1c59cba..33029c3 100644 (file)
@@ -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];
index fb28f67..511f953 100644 (file)
--- 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)
 {
index efda7b0..a7d39ea 100644 (file)
@@ -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);