From cc8d1bcff205d236669f07a4ad2b14aeaf8130ac Mon Sep 17 00:00:00 2001 From: Egor Date: Thu, 25 Jan 2024 04:22:32 +0200 Subject: [PATCH] add SASL EXTERNAL support --- data/.gitignore | 1 + src/common.h | 1 + src/irc.c | 107 ++++++++++++++++++++++++++---------------------- src/passwd.c | 23 +++++++++++ src/passwd.h | 1 + 5 files changed, 85 insertions(+), 48 deletions(-) diff --git a/data/.gitignore b/data/.gitignore index d21035f..63bc720 100644 --- a/data/.gitignore +++ b/data/.gitignore @@ -7,3 +7,4 @@ users.txt newusers.txt help.txt motd.txt +certfp.txt diff --git a/src/common.h b/src/common.h index 33029c3..af8b8cd 100644 --- a/src/common.h +++ b/src/common.h @@ -71,6 +71,7 @@ struct client_data { unsigned dying :1; unsigned write_error :1; unsigned poll_reg :2; + unsigned saslexternal :1; }; struct channel { char *key; diff --git a/src/irc.c b/src/irc.c index 7991c48..25ba04e 100644 --- a/src/irc.c +++ b/src/irc.c @@ -216,7 +216,7 @@ int processPacket(struct client_data *client, char *source, char *command, int a 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%s\r\n", hostname, - client->umode & UMODE_SECURE ? (client->caps & CAP_302 ? " sasl=PLAIN" : " sasl") : ""); + client->umode & UMODE_SECURE ? (client->caps & CAP_302 ? " sasl=PLAIN,EXTERNAL" : " sasl") : ""); sendbuf(client); if (!is_registered(client)) { client->regstate = REG_CAPLS; @@ -276,14 +276,15 @@ int processPacket(struct client_data *client, char *source, char *command, int a 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; + if (strcmp(args[0], "PLAIN") != 0 && strcmp(args[0], "EXTERNAL") != 0) + return copy_strf(&databuf, ":%s 908 * PLAIN,EXTERNAL :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; + client->saslexternal = !strcmp(args[0], "EXTERNAL"); if (!client->saslbuf) abort(); } else { @@ -310,56 +311,66 @@ int processPacket(struct client_data *client, char *source, char *command, int a 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; + if (client->saslbufLen != 0) { + while (client->saslbufLen & 3) + client->saslbuf[client->saslbufLen++] = '='; + 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; + } + } + unsigned char *p, *q; + p = (unsigned char *)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 = (unsigned char *)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-(unsigned char *)client->saslbuf - parity; } - unsigned char *p, *q; - p = (unsigned char *)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 { + client->saslbuf[client->saslbufLen] = '\0'; + const char *username; + int res = -1; + if (!client->saslexternal) { + 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; + 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)); + } else { +#ifdef HAS_TLS + if (client->cctx && tls_peer_cert_provided(client->cctx)) { + username = db_read_rhs(tls_peer_cert_hash(client->cctx), getdatafile("certfp.txt")); + res = -!username; } - p++; - } - p = q = (unsigned char *)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; +#endif } - client->saslbufLen = q-(unsigned char *)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); diff --git a/src/passwd.c b/src/passwd.c index e0d1c62..fadf685 100644 --- a/src/passwd.c +++ b/src/passwd.c @@ -107,3 +107,26 @@ int password_check(const char *key, const char *pw, const char *fn) return -1; } #endif +const char *db_read_rhs(const char *key, const char *fn) { + FILE *fi = fopen(fn, "rb"); + if (!fi) + return 0; + int keylen = strlen(key); + static char buf[1024]; + while (fgets(buf, 1024, fi)) { + char *rhs = strchr(buf, ':'); + if (rhs) { + rhs++; + int len = strlen(rhs); + if (rhs[len-1] == '\n') + rhs[--len] = '\0'; + if (keylen == len && memcmp(rhs, key, keylen) == 0) { + rhs[-1] = '\0'; + fclose(fi); + return buf; + } + } + } + fclose(fi); + return 0; +} diff --git a/src/passwd.h b/src/passwd.h index d33f5aa..61e00b3 100644 --- a/src/passwd.h +++ b/src/passwd.h @@ -3,4 +3,5 @@ const char *password_hash(const char *pw); int password_set(const char *key, const char *pw, const char *fn, const char *tmpfn); int password_check(const char *key, const char *pw, const char *fn); +const char *db_read_rhs(const char *key, const char *fn); #endif -- 2.56.0