diff --git a/eggdrop.conf b/eggdrop.conf index 8ab6a1a72..93ea11253 100755 --- a/eggdrop.conf +++ b/eggdrop.conf @@ -1113,12 +1113,18 @@ server add ssl.example.net +7000 # 1 = ECDSA-NIST256P-CHALLENGE (Uses a certificate; usually requires a # public key to be registered with NickServ # or other similar service. Set certificate -# to use in sasl-ecdsa-key setting below) +# to use in sasl-ecdsa-key setting below. +# Beware: NIST curve could be backdoored, +# so please use EXTERNAL or SCRAM instead.) # # 2 = EXTERNAL (Some other method you set up. Certificates # used are defined in ssl-certificate and # ssl-privatekey settings in SSL section) # +# 3 = SCRAM-SHA-256 +# +# 4 = SCRAM-SHA-512 +# #set sasl-mechanism 0 # Set username to authenticate to IRC NickServ with diff --git a/src/mod/server.mod/Makefile b/src/mod/server.mod/Makefile index 1b26a1043..8f84cc8d8 100644 --- a/src/mod/server.mod/Makefile +++ b/src/mod/server.mod/Makefile @@ -40,5 +40,5 @@ distclean: clean .././server.mod/server.h .././server.mod/isupport.c \ .././server.mod/tclisupport.c .././server.mod/servmsg.c \ .././server.mod/../irc.mod/irc.h \ - .././server.mod/../channels.mod/channels.h .././server.mod/cmdsserv.c \ - .././server.mod/tclserv.c + .././server.mod/../channels.mod/channels.h .././server.mod/sasl.c \ + .././server.mod/cmdsserv.c .././server.mod/tclserv.c diff --git a/src/mod/server.mod/sasl.c b/src/mod/server.mod/sasl.c new file mode 100644 index 000000000..234b93116 --- /dev/null +++ b/src/mod/server.mod/sasl.c @@ -0,0 +1,716 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * sasl.c -- part of server.mod + * + * Written by Michael Ortmann + * + * Copyright (C) 2019 - 2024 Eggheads Development Team + */ + +#undef answer /* before resolv.h because it could collide with src/mod/module.h + * (dietlibc) */ +#include /* base64 encode b64_ntop() and base64 decode b64_pton() */ + +/* RFC 5802 - printable ASCII characters excluding ',' + * printable = %x21-2B / %x2D-7E + */ +#define CHARSET_SCRAM "\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e" + +#define CLIENT_KEY "Client Key" +#define SERVER_KEY "Server Key" + +/* Available sasl mechanisms */ +enum { + SASL_MECHANISM_PLAIN, + SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE, + SASL_MECHANISM_EXTERNAL, + SASL_MECHANISM_SCRAM_SHA_256, + SASL_MECHANISM_SCRAM_SHA_512, + /* TODO: https://github.com/atheme/atheme/blob/master/modules/saslserv/ecdh-x25519-challenge.c */ + /* SASL_MECHANISM_ECDH_X25519_CHALLENGE, */ + SASL_MECHANISM_NUM +}; + +#define SASL_PASSWORD_MAX 120 +#define SASL_ECDSA_KEY_MAX 120 + +static int sasl_timeout_time = 0; +static int sasl_continue = 1; +static char sasl_username[NICKMAX + 1]; +static int sasl_mechanism = 0; +static char sasl_password[SASL_PASSWORD_MAX + 1]; +static char sasl_ecdsa_key[SASL_ECDSA_KEY_MAX + 1]; +static int sasl_timeout = 15; +int sasl = 0; + +/* Available sasl mechanisms. */ +static char const *SASL_MECHANISMS[SASL_MECHANISM_NUM] = { + [SASL_MECHANISM_PLAIN] = "PLAIN", + [SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE] = "ECDSA-NIST256P-CHALLENGE", + [SASL_MECHANISM_EXTERNAL] = "EXTERNAL", + [SASL_MECHANISM_SCRAM_SHA_256] = "SCRAM-SHA-256", + [SASL_MECHANISM_SCRAM_SHA_512] = "SCRAM-SHA-512", + /* [SASL_MECHANISM_ECDH_X25519_CHALLENGE] = "ECDH-X25519-CHALLENGE", */ +}; + +/* scram state */ +#ifdef TLS +#if OPENSSL_VERSION_NUMBER >= 0x10000000L /* 1.0.0 */ +const EVP_MD *digest; +char salted_password[EVP_MAX_MD_SIZE]; +static int step = 0; +char nonce[21]; /* atheme defines acceptable client nonce len min 8 max 512 chars + * nonce 128 bit = math.ceil(128 / math.log(93, 2)) = 20 chars + * 3 major irc clients and postgres use 18, looks like ripping is still a thing ;) + */ +char client_first_message[1024]; +int digest_len, auth_message_len; +char auth_message[3069]; +/* a client implementation MAY cache ClientKey&ServerKey */ +char last_sasl_password[sizeof sasl_password]; +char last_salt_b64[96] = ""; +char last_i[32] = ""; +char client_key[EVP_MAX_MD_SIZE]; +unsigned int client_key_len; +int use_cache; +char server_key[EVP_MAX_MD_SIZE]; +unsigned int server_key_len; +#endif /* OPENSSL_VERSION_NUMBER >= 0x10000000L */ +#endif /* TLS */ + +static void sasl_error(const char *msg) +{ + putlog(LOG_SERV, "*", "SASL: error: %s", msg); + dprintf(DP_MODE, "CAP END\n"); + sasl_timeout_time = 0; + if (!sasl_continue) { + putlog(LOG_DEBUG, "*", "SASL: Aborting connection and retrying"); + nuke_server("sasl"); + } +} + +static void sasl_secondly() +{ + if (!--sasl_timeout_time) + sasl_error("timeout"); +} + +/* Got 901: RPL_LOGGEDOUT, users account name is unset (whether by SASL or + * otherwise) + */ +static int got901(char *from, char *msg) +{ + newsplit(&msg); /* nick */ + newsplit(&msg); /* nick!ident@host */ + fixcolon(msg); + putlog(LOG_SERV, "*", "%s: %s", from, msg); + return 0; +} + +/* Got 902: ERR_NICKLOCKED, authentication fails b/c nick is unavailable + * Got 904: ERR_SASLFAIL, invalid credentials (or something not covered) + * Got 905: ERR_SASLTOOLONG, AUTHENTICATE command was too long (>400 bytes) + * Got 906: ERR_SASL_ABORTED, sent AUTHENTICATE command with * as parameter + * For easy grepping, this covers got902 got904 got905 got906 + */ +static int gotsasl90X(char *from, char *msg) +{ + newsplit(&msg); /* nick */ + fixcolon(msg); + sasl_error(msg); + return 0; +} + +/* Got 903: RPL_SASLSUCCESS, authentication successful */ +static int got903(char *from, char *msg) +{ + newsplit(&msg); /* nick */ + fixcolon(msg); + putlog(LOG_SERV, "*", "SASL: %s", msg); + dprintf(DP_MODE, "CAP END\n"); + sasl_timeout_time = 0; + return 0; +} + +/* Got 907: ERR_SASLALREADY, already authenticated */ +static int got907(char *from, char *msg) +{ + putlog(LOG_SERV, "*", "SASL: Already authenticated"); + return 0; +} + +/* Got 908: RPL_SASLMECHS, available mechanisms by network */ +static int got908(char *from, char *msg) +{ + char s[128]; + + newsplit(&msg); /* nick */ + fixcolon(msg); + putlog(LOG_SERV, "*", "SASL: Available mechanisms: %s", msg); + del_capability("sasl"); + snprintf(s, sizeof s, "sasl=%s", msg); + add_capabilities(s); + return 0; +} + +static int sasl_plain(char *dst, size_t dstsize) +{ + /* Don't use snprintf() due to \0 inside + * and don't use stpcpy() for it is POSIX 2008 + */ + size_t n, y = 0; + + n = strlcpy(dst, sasl_username, dstsize); + dst = dst + n + 1; + dstsize = dstsize - n - 1; + y = n + 1; + n = strlcpy(dst, sasl_username, dstsize); + dst = dst + n + 1; + dstsize = dstsize - n - 1; + y = y + n + 1; + n = strlcpy(dst, sasl_password, dstsize); + return y + n; +} + +#ifdef TLS +static int sasl_ecdsa_nist256p_challange_step_0(char *dst, size_t dstsize) +{ + /* Don't use snprintf() due to \0 inside + * and don't use stpcpy() for it is POSIX 2008 + */ + size_t n, y = 0; + + n = strlcpy(dst, sasl_username, dstsize); + dst = dst + n + 1; + dstsize = dstsize - n - 1; + y = n + 1; + n = strlcpy(dst, sasl_username, dstsize); + return y + n; +} + +static int sasl_ecdsa_nist256p_challange_step_1( + char *restrict client_msg_plain, char *restrict server_msg_plain, + int server_msg_plain_len) +{ + FILE *fp; + char error_msg[256]; /* snprintf() truncation should be tolerable */ + EVP_PKEY *pkey; + + if (!(fp = fopen(sasl_ecdsa_key, "r"))) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: could not open " + "file sasl_ecdsa_key %s: %s\n", sasl_ecdsa_key, strerror(errno)); + sasl_error(error_msg); + return -1; + } + if (!(pkey = PEM_read_PrivateKey(fp, NULL, 0, NULL))) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: " + "PEM_read_PrivateKey(): SSL error = %s\n", + ERR_error_string(ERR_get_error(), 0)); + sasl_error(error_msg); + fclose(fp); + return -1; + } + fclose(fp); +#if OPENSSL_VERSION_NUMBER >= 0x10000000L /* 1.0.0 */ + EVP_PKEY_CTX *ctx; + size_t siglen; + + /* The EVP interface to digital signatures should almost always be used in + * preference to the low level interfaces. + */ + if (!(ctx = EVP_PKEY_CTX_new(pkey, NULL))) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: EVP_PKEY_CTX_new(): " + "SSL error = %s\n", ERR_error_string(ERR_get_error(), 0)); + sasl_error(error_msg); + return -1; + } + EVP_PKEY_free(pkey); + if (EVP_PKEY_sign_init(ctx) <= 0) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: EVP_PKEY_sign_init():" + "SSL error = %s\n", ERR_error_string(ERR_get_error(), 0)); + sasl_error(error_msg); + EVP_PKEY_CTX_free(ctx); + return -1; + } + if (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: " + "EVP_PKEY_CTX_set_signature_md(): SSL error = %s\n", + ERR_error_string(ERR_get_error(), 0)); + sasl_error(error_msg); + EVP_PKEY_CTX_free(ctx); + return -1; + } + /* EVP_PKEY_sign() must be used instead of EVP_DigestSign*() and EVP_Sign*(), + * because EVP_PKEY_sign() does not hash the data to be signed. + * EVP_PKEY_sign() is for signing digests, EVP_DigestSign*() and EVP_Sign*() + * are for signing messages. + */ + if (EVP_PKEY_sign(ctx, NULL, &siglen, (unsigned char *) server_msg_plain, server_msg_plain_len) <= 0) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: EVP_PKEY_sign(): SSL " + "error = %s\n", ERR_error_string(ERR_get_error(), 0)); + sasl_error(error_msg); + EVP_PKEY_CTX_free(ctx); + return -1; + } + if (EVP_PKEY_sign(ctx, (unsigned char *) client_msg_plain, &siglen, (unsigned char *) server_msg_plain, server_msg_plain_len) <= 0) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: EVP_PKEY_sign(): SSL " + "error = %s\n", ERR_error_string(ERR_get_error(), 0)); + sasl_error(error_msg); + EVP_PKEY_CTX_free(ctx); + return -1; + } + EVP_PKEY_CTX_free(ctx); +#else + EC_KEY *eckey; + int ret; + unsigned int siglen; + + eckey = EVP_PKEY_get1_EC_KEY(pkey); + EVP_PKEY_free(pkey); + if (!eckey) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: " + "EVP_PKEY_get1_EC_KEY(): SSL error = %s\n", + ERR_error_string(ERR_get_error(), 0)); + sasl_error(error_msg); + return -1; + } + ret = ECDSA_sign(0, (const unsigned char *) server_msg_plain, + server_msg_plain_len, + (unsigned char *) client_msg_plain, &siglen, eckey); + EC_KEY_free(eckey); + if (!ret) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: ECDSA_sign() SSL " + "error = %s\n", ERR_error_string(ERR_get_error(), 0)); + sasl_error(error_msg); + return -1; + } +#endif /* OPENSSL_VERSION_NUMBER >= 0x10000000L */ + return siglen; +} + +#if OPENSSL_VERSION_NUMBER >= 0x10000000L /* 1.0.0 */ +static int sasl_scram_step_0(char *client_msg_plain, int client_msg_plain_len) +{ + /* TODO: after merge of #1706 make_rand_str_from_chars() should be made + * return unbiased uniformed randoms + */ + make_rand_str_from_chars(nonce, (sizeof nonce) - 1, CHARSET_SCRAM); + snprintf(client_msg_plain, client_msg_plain_len, "n,,n=%s,r=%s", + sasl_username, nonce); + return strlcpy(client_first_message, client_msg_plain, + sizeof client_first_message); +} + +static int sasl_scram_step_1(char *restrict client_msg_plain, + int client_msg_plain_len, + char *restrict server_msg_plain) +{ + char server_first_message[1024]; + char *word, *brkb, *server_nonce = 0, *salt_b64 = 0, *i = 0; + char error_msg[128]; /* snprintf() truncation should be tolerable */ + int salt_plain_len, iter, j, ret; + char salt_plain[64]; /* atheme: Valid values are 8 to 64 (inclusive) */ + unsigned int stored_key_len; + unsigned char stored_key[EVP_MAX_MD_SIZE]; + char client_final_message_without_proof[1024]; + unsigned char client_signature[EVP_MAX_MD_SIZE]; + unsigned char client_proof[EVP_MAX_MD_SIZE]; + char client_proof_b64[1024]; + struct rusage ru1, ru2; + + strlcpy(server_first_message, server_msg_plain, sizeof server_first_message); + for (word = strtok_r(server_msg_plain, ",", &brkb); + word; + word = strtok_r(NULL, ",", &brkb)) { + switch (*word) { + case 'r': + if ( +#if OPENSSL_VERSION_NUMBER >= 0x1010008fL /* 1.1.0h */ + CRYPTO_memcmp +#else + memcmp +#endif + (word + 2, nonce, (sizeof nonce) - 1)) { + sasl_error("AUTHENTICATE: server nonce != client nonce"); + return -1; + } + server_nonce = word + 2; + break; + case 's': + salt_b64 = word + 2; + break; + case 'i': + i = word + 2; + break; + case 'e': + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: server error: %s", word + 2); + sasl_error(error_msg); + return -1; + default: + putlog(LOG_SERV, "*", "SASL: AUTHENTICATE warning: SCRAM Attribute ignored: %s", word); + } + } + if (!server_nonce) { + sasl_error("AUTHENTICATE: server nonce missing from SCRAM challenge"); + return -1; + } + if (!salt_b64) { + sasl_error("AUTHENTICATE: salt missing from SCRAM challenge"); + return -1; + } + if (!i) { + sasl_error("AUTHENTICATE: iteration count missing from SCRAM challenge"); + return -1; + } + /* TODO: normalize(password) + * Eggdrop doesnt have support for utf8 normalization yet + * tcl also doesnt have it in core yet, only in tcllib + * We could use glib or something, but we dont want dependency bloat just + * for one function + */ + + /* ClientKey := HMAC(SaltedPassword, "Client Key") */ + + use_cache = (!strcmp(sasl_password, last_sasl_password)) && (!strcmp(salt_b64, last_salt_b64)) && (!strcmp(i, last_i)); + + if (!use_cache) { + if ((salt_plain_len = b64_pton(salt_b64, (unsigned char*) salt_plain, sizeof salt_plain)) == -1) { + sasl_error("AUTHENTICATE: could not base64 decode salt"); + return -1; + } + errno = 0; + iter = strtol(i, NULL, 10); + if (errno) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: strtol(%s): %s", i, strerror(errno)); + sasl_error(error_msg); + return -1; + } + + if (sasl_mechanism == SASL_MECHANISM_SCRAM_SHA_256) + digest = EVP_sha256(); + else + digest = EVP_sha512(); + digest_len = EVP_MD_size(digest); + + ret = getrusage(RUSAGE_SELF, &ru1); + if (!PKCS5_PBKDF2_HMAC(sasl_password, strlen(sasl_password), + (const unsigned char *) salt_plain, salt_plain_len, + iter, digest, digest_len, + (unsigned char *) salted_password)) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: " + "PKCS5_PBKDF2_HMAC(): %s", ERR_error_string(ERR_get_error(), + NULL)); + sasl_error(error_msg); + return -1; + } + if (!ret && !getrusage(RUSAGE_SELF, &ru2)) { + debug4("SASL: pbkdf2 digest %s iter %i, user %.3fms sys %.3fms", EVP_MD_name(digest), + iter, + (double) (ru2.ru_utime.tv_usec - ru1.ru_utime.tv_usec) / 1000 + + (double) (ru2.ru_utime.tv_sec - ru1.ru_utime.tv_sec ) * 1000, + (double) (ru2.ru_stime.tv_usec - ru1.ru_stime.tv_usec) / 1000 + + (double) (ru2.ru_stime.tv_sec - ru1.ru_stime.tv_sec ) * 1000); + } + else { + debug1("PBKDF2 error: getrusage(): %s", strerror(errno)); + } + + if (!HMAC(digest, salted_password, digest_len, (unsigned char *) CLIENT_KEY, + strlen(CLIENT_KEY), (unsigned char *) client_key, + &client_key_len)) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: HMAC(): %s", + ERR_error_string(ERR_get_error(), NULL)); + sasl_error(error_msg); + return -1; + } + strlcpy(last_sasl_password, sasl_password, sizeof last_sasl_password); + strlcpy(last_salt_b64, salt_b64, sizeof last_salt_b64); + strlcpy(last_i, i, sizeof last_i); + } + else + debug0("SASL: using cached client and server key"); + + /* StoredKey := H(ClientKey) */ + + if (!EVP_Digest(client_key, client_key_len, stored_key, &stored_key_len, digest, NULL)) { + snprintf(error_msg, sizeof error_msg, + "AUTHENTICATE: EVP_Digest(): %s", + ERR_error_string(ERR_get_error(), NULL)); + sasl_error(error_msg); + return -1; + } + + /* AuthMessage := client-first-message-bare + "," + + * server-first-message + "," + + * client-final-message-without-proof + */ + + snprintf(client_final_message_without_proof, + sizeof client_final_message_without_proof, "c=biws,r=%s", + server_nonce); + + auth_message_len = snprintf(auth_message, sizeof auth_message, "%s,%s,%s", + client_first_message + 3, server_first_message, + client_final_message_without_proof); + + /* ClientSignature := HMAC(StoredKey, AuthMessage) */ + + if (!HMAC(digest, stored_key, digest_len, (unsigned char *) auth_message, + auth_message_len, client_signature, NULL)) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: HMAC(): %s", + ERR_error_string(ERR_get_error(), NULL)); + sasl_error(error_msg); + return -1; + } + + /* ClientProof := ClientKey XOR ClientSignature */ + + for (j = 0; j < client_key_len; j++) + client_proof[j] = client_key[j] ^ client_signature[j]; + + if (b64_ntop(client_proof, client_key_len, client_proof_b64, sizeof client_proof_b64) == -1) { + sasl_error("AUTHENTICATE: could not base64 encode"); + return -1; + } + + return snprintf(client_msg_plain, client_msg_plain_len, "%s,p=%s", + client_final_message_without_proof, client_proof_b64); +} + +static void sasl_scram_step_2(char *restrict client_msg_plain, + int client_msg_plain_len, + char *restrict server_msg_plain) +{ + char error_msg[128]; /* snprintf() truncation should be tolerable */ + unsigned char server_signature[EVP_MAX_MD_SIZE]; + char server_signature_b64[128]; + int server_signature_b64_len; + + /* ServerKey := HMAC(SaltedPassword, "Server Key") */ + + if ((!use_cache) && + (!HMAC(digest, salted_password, digest_len, (unsigned char *) SERVER_KEY, + strlen(SERVER_KEY), (unsigned char *) server_key, + &server_key_len))) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: HMAC(): %s", + ERR_error_string(ERR_get_error(), NULL)); + sasl_error(error_msg); + return; + } + + /* ServerSignature := HMAC(ServerKey, AuthMessage) */ + + if (!HMAC(digest, server_key, digest_len, (unsigned char *) auth_message, + auth_message_len, server_signature, NULL)) { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: HMAC(): %s", + ERR_error_string(ERR_get_error(), NULL)); + sasl_error(error_msg); + return; + } + + if ((server_signature_b64_len = b64_ntop(server_signature, digest_len, server_signature_b64, sizeof server_signature_b64)) == -1) { + sasl_error("AUTHENTICATE: could not base64 encode"); + return; + } + + if ( +#if OPENSSL_VERSION_NUMBER >= 0x1010008fL /* 1.1.0h */ + CRYPTO_memcmp +#else + memcmp +#endif + (server_msg_plain + 2, server_signature_b64, server_signature_b64_len)) { + sasl_error("invalid server signature"); + return; + } + + putlog(LOG_SERV, "*", "SASL: authentication of server successful"); + dprintf(DP_MODE, "AUTHENTICATE +\n"); + sasl_timeout_time = 0; +} +#endif /* OPENSSL_VERSION_NUMBER >= 0x10000000L */ +#endif /* TLS */ + +static int gotauthenticate(char *from, char *msg) +{ + char client_msg_plain[1024]; + int client_msg_plain_len; +#ifdef TLS + char server_msg_plain[1024]; + char error_msg[1050]; /* snprintf() truncation should be tolerable */ + int server_msg_plain_len; +#endif + #ifndef MAX + #define MAX(a,b) (((a)>(b))?(a):(b)) + #endif + char client_msg_b64[((MAX((sizeof client_msg_plain), 400) + 2) / 3) << 2] = ""; + + fixcolon(msg); /* Because Inspircd does its own thing */ +#ifdef TLS + if (*msg == '+') { +#endif + if ((sasl_mechanism != SASL_MECHANISM_EXTERNAL) && (!*sasl_username)) { + putlog(LOG_SERV, "*", "SASL: sasl-username not set, setting it to " + "username %s", botname); + strlcpy(sasl_username, botuser, sizeof sasl_username); + } +#ifdef TLS + switch (sasl_mechanism) { + case SASL_MECHANISM_PLAIN: +#endif + client_msg_plain_len = sasl_plain(client_msg_plain, sizeof client_msg_plain); +#ifdef TLS + break; + case SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE: + client_msg_plain_len = sasl_ecdsa_nist256p_challange_step_0(client_msg_plain, sizeof client_msg_plain); + break; + case SASL_MECHANISM_EXTERNAL: + dprintf(DP_MODE, "AUTHENTICATE +\n"); + return 0; +#if OPENSSL_VERSION_NUMBER >= 0x10000000L /* 1.0.0 */ + case SASL_MECHANISM_SCRAM_SHA_256: + case SASL_MECHANISM_SCRAM_SHA_512: + client_msg_plain_len = sasl_scram_step_0(client_msg_plain, sizeof client_msg_plain); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10000000L */ + } + } else { + if ((server_msg_plain_len = b64_pton(msg, (unsigned char*) server_msg_plain, sizeof server_msg_plain)) == -1) { + sasl_error("AUTHENTICATE: could not base64 decode line from server"); + return 0; + } + if (server_msg_plain_len < 2) { + sasl_error("AUTHENTICATE: server message too short"); + return 0; + } + if (*server_msg_plain == 'e') { + snprintf(error_msg, sizeof error_msg, "AUTHENTICATE: server error: %s", server_msg_plain + 2); + sasl_error(error_msg); + return 0; + } + if (sasl_mechanism == SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE) { + if ((client_msg_plain_len = sasl_ecdsa_nist256p_challange_step_1(client_msg_plain, server_msg_plain, server_msg_plain_len)) < 0) + return 0; + } +#if OPENSSL_VERSION_NUMBER >= 0x10000000L /* 1.0.0 */ + else + if (step == 0) { + if ((client_msg_plain_len = sasl_scram_step_1(client_msg_plain, sizeof client_msg_plain, server_msg_plain)) < 0) + return 0; + step++; + } else { + sasl_scram_step_2(client_msg_plain, sizeof client_msg_plain, server_msg_plain); + step = 0; + return 0; + } +#endif /* OPENSSL_VERSION_NUMBER >= 0x10000000L */ + } +#endif /* TLS */ + if (b64_ntop((unsigned char *) client_msg_plain, client_msg_plain_len, client_msg_b64, sizeof client_msg_b64) == -1) { + sasl_error("AUTHENTICATE: could not base64 encode"); + return 0; + } + dprintf(DP_MODE, "AUTHENTICATE %s\n", client_msg_b64); + return 0; +} + +static char *traced_sasl_mechanism(ClientData cdata, Tcl_Interp *irp, + EGG_CONST char *name1, + EGG_CONST char *name2, int flags) +{ + if ((sasl_mechanism < 0) || (sasl_mechanism >= SASL_MECHANISM_NUM)) + return "sasl-mechanism is not set to an allowed value, please check it and" + " try again"; +#ifdef TLS +#ifndef HAVE_EVP_PKEY_GET1_EC_KEY + if (sasl_mechanism == SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE) + return "SASL NIST256P functionality missing from your TLS libs, please " + "choose a different SASL method"; +#endif /* HAVE_EVP_PKEY_GET1_EC_KEY */ +#if OPENSSL_VERSION_NUMBER < 0x10000000L /* 1.0.0 */ + if ((sasl_mechanism == SASL_MECHANISM_SCRAM_SHA_256) || + (sasl_mechanism == SASL_MECHANISM_SCRAM_SHA_512)) + return "SASL SCRAM functionality needs openssl version 1.0.0 or higher, " + "please choose a different SASL method"; +#endif /* OPENSSL_VERSION_NUMBER < 0x10000000L */ +#else /* TLS */ + if (sasl_mechanism != SASL_MECHANISM_PLAIN) + return "The selected SASL authentication method requires TLS libraries " + "which are not installed on this machine. Please choose the PLAIN " + "method."; +#endif /* TLS */ + return NULL; +} + +static cmd_t sasl_raw[] = { + {"901", "", (IntFunc) got901, NULL}, + {"902", "", (IntFunc) gotsasl90X, NULL}, + {"903", "", (IntFunc) got903, NULL}, + {"904", "", (IntFunc) gotsasl90X, NULL}, + {"905", "", (IntFunc) gotsasl90X, NULL}, + {"906", "", (IntFunc) gotsasl90X, NULL}, + {"907", "", (IntFunc) got907, NULL}, + {"908", "", (IntFunc) got908, NULL}, + {"AUTHENTICATE", "", (IntFunc) gotauthenticate, NULL}, + {NULL, NULL, NULL, NULL} +}; + +static tcl_ints sasl_tcl_ints[] = { + {"sasl", &sasl, 0}, + {"sasl-mechanism", &sasl_mechanism, 0}, + {"sasl-continue", &sasl_continue, 0}, + {"sasl-timeout", &sasl_timeout, 0}, + {NULL, NULL, 0} +}; + +static tcl_strings sasl_tcl_strings[] = { + {"sasl-username", sasl_username, NICKMAX, 0}, + {"sasl-password", sasl_password, SASL_PASSWORD_MAX, 0}, + {"sasl-ecdsa-key", sasl_ecdsa_key, SASL_ECDSA_KEY_MAX, 0}, + {NULL, NULL, 0, 0} +}; + +static void sasl_close() +{ + rem_builtins(H_raw, sasl_raw); + rem_tcl_ints(sasl_tcl_ints); + rem_tcl_strings(sasl_tcl_strings); + Tcl_UntraceVar(interp, "sasl-mechanism", TCL_TRACE_WRITES | TCL_TRACE_UNSETS, + traced_sasl_mechanism, NULL); +} + +static void sasl_start() +{ + Tcl_TraceVar(interp, "sasl-mechanism", TCL_TRACE_WRITES | TCL_TRACE_UNSETS, + traced_sasl_mechanism, NULL); + add_builtins(H_raw, sasl_raw); + add_tcl_ints(sasl_tcl_ints); + add_tcl_strings(sasl_tcl_strings); +} + +/* There are two forms of the AUTHENTICATE command: initial client message and + * later messages. The initial client message specifies the SASL mechanism to + * be used. +*/ +int sasl_authenticate_initial(const struct cap_values *cap_value_list) +{ + char error_msg[128]; + putlog(LOG_DEBUG, "*", "SASL: Starting authentication process"); +#ifdef TLS + int servidx = findanyidx(serv); + if ((sasl_mechanism == SASL_MECHANISM_EXTERNAL) && !dcc[servidx].ssl) { + sasl_error("authentication mechanism EXTERNAL not possible via non-ssl connection"); + return 1; + } +#endif + if (!is_cap_value(cap_value_list, SASL_MECHANISMS[sasl_mechanism])) { + snprintf(error_msg, sizeof error_msg, + "authentication mechanism %s not supported by server", + SASL_MECHANISMS[sasl_mechanism]); /* TODO: report server supported mechanisms */ + sasl_error(error_msg); + return 1; + } + putlog(LOG_DEBUG, "*", "SASL: AUTHENTICATE %s", SASL_MECHANISMS[sasl_mechanism]); + dprintf(DP_MODE, "AUTHENTICATE %s\n", SASL_MECHANISMS[sasl_mechanism]); + sasl_timeout_time = sasl_timeout; + return 0; +} diff --git a/src/mod/server.mod/server.c b/src/mod/server.mod/server.c index 6aff0e488..5f12aa982 100644 --- a/src/mod/server.mod/server.c +++ b/src/mod/server.mod/server.c @@ -24,7 +24,6 @@ #define MODULE_NAME "server" #define MAKING_SERVER -#include #include "src/mod/module.h" #include "server.h" @@ -129,23 +128,16 @@ static int add_server(const char *, const char *, const char *); static int del_server(const char *, const char *); static void free_server(struct server_list *); -static int sasl = 0; static int away_notify = 0; static int invite_notify = 0; static int message_tags = 0; static char cap_request[CAPMAX - 9]; -static int sasl_mechanism = 0; -static char sasl_username[NICKMAX + 1]; -static char sasl_password[81]; -static int sasl_continue = 1; -static char sasl_ecdsa_key[121]; -static int sasl_timeout = 15; -static int sasl_timeout_time = 0; #include "isupport.c" #include "tclisupport.c" #include "servmsg.c" +#include "sasl.c" #define MAXPENALTY 10 @@ -157,13 +149,6 @@ static int burst; #include "cmdsserv.c" #include "tclserv.c" -/* Available sasl mechanisms. */ -char const *SASL_MECHANISMS[SASL_MECHANISM_NUM] = { - [SASL_MECHANISM_PLAIN] = "PLAIN", - [SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE] = "ECDSA-NIST256P-CHALLENGE", - [SASL_MECHANISM_EXTERNAL] = "EXTERNAL" -}; - static void write_to_server(char *s, unsigned int len) { char *s2 = nmalloc(len + 2); @@ -1734,19 +1719,16 @@ static char *traced_nicklen(ClientData cdata, Tcl_Interp *irp, } static tcl_strings my_tcl_strings[] = { - {"botnick", NULL, 0, STR_PROTECT}, - {"altnick", altnick, NICKMAX, 0}, - {"realname", botrealname, 80, 0}, - {"init-server", initserver, 120, 0}, - {"connect-server", connectserver, 120, 0}, - {"stackable-commands", stackablecmds, 510, 0}, - {"stackable2-commands", stackable2cmds, 510, 0}, - {"cap-request", cap_request, CAPMAX - 9, 0}, - {"sasl-username", sasl_username, NICKMAX, 0}, - {"sasl-password", sasl_password, 80, 0}, - {"sasl-ecdsa-key", sasl_ecdsa_key, 120, 0}, - {"net-type", net_type, 8, 0}, - {NULL, NULL, 0, 0} + {"botnick", NULL, 0, STR_PROTECT}, + {"altnick", altnick, NICKMAX, 0}, + {"realname", botrealname, 80, 0}, + {"init-server", initserver, 120, 0}, + {"connect-server", connectserver, 120, 0}, + {"stackable-commands", stackablecmds, 510, 0}, + {"stackable2-commands", stackable2cmds, 510, 0}, + {"cap-request", cap_request, CAPMAX - 9, 0}, + {"net-type", net_type, 8, 0}, + {NULL, NULL, 0, 0} }; static tcl_coups my_tcl_coups[] = { @@ -1784,10 +1766,6 @@ static tcl_ints my_tcl_ints[] = { #ifdef TLS {"ssl-verify-server", &tls_vfyserver, 0}, #endif - {"sasl", &sasl, 0}, - {"sasl-mechanism", &sasl_mechanism, 0}, - {"sasl-continue", &sasl_continue, 0}, - {"sasl-timeout", &sasl_timeout, 0}, {"away-notify", &away_notify, 0}, {"invite-notify", &invite_notify, 0}, {"message-tags", &message_tags, 0}, @@ -2034,8 +2012,8 @@ static void server_secondly() deq_msg(); if (!resolvserv && serv < 0) connect_server(); - if (!--sasl_timeout_time) - handle_sasl_timeout(); + else + sasl_secondly(); } static void server_5minutely() @@ -2310,6 +2288,7 @@ static char *server_close() del_hook(HOOK_PRE_REHASH, (Function) server_prerehash); del_hook(HOOK_REHASH, (Function) server_postrehash); del_hook(HOOK_DIE, (Function) server_die); + sasl_close(); module_undepend(MODULE_NAME); return NULL; } @@ -2508,27 +2487,6 @@ char *server_start(Function *global_funcs) my_tcl_strings[0].buf = botname; add_tcl_strings(my_tcl_strings); add_tcl_ints(my_tcl_ints); - if (sasl) { - if ((sasl_mechanism < 0) || (sasl_mechanism >= SASL_MECHANISM_NUM)) { - fatal("ERROR: sasl-mechanism is not set to an allowed value, please check" - " it and try again", 0); - } -#ifdef TLS -#ifndef HAVE_EVP_PKEY_GET1_EC_KEY - if (sasl_mechanism == SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE) { - fatal("ERROR: NIST256 functionality missing from your TLS libs, please " - "choose a different SASL method", 0); - } -#endif /* HAVE_EVP_PKEY_GET1_EC_KEY */ -#else /* TLS */ - if ((sasl_mechanism == SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE) || - (sasl_mechanism == SASL_MECHANISM_EXTERNAL)) { - fatal("ERROR: The selected SASL authentication method requires TLS " - "libraries which are not installed on this machine. Please " - "choose the PLAIN method in your config.", 0); - } -#endif /* TLS */ - } add_tcl_commands(my_tcl_cmds); add_tcl_coups(my_tcl_coups); add_hook(HOOK_SECONDLY, (Function) server_secondly); @@ -2548,5 +2506,6 @@ char *server_start(Function *global_funcs) curserv = 999; /* Because this reads the interp variable, the read trace MUST be after */ do_nettype(); + sasl_start(); return NULL; } diff --git a/src/mod/server.mod/server.h b/src/mod/server.mod/server.h index ac81a5422..17b3d9e80 100644 --- a/src/mod/server.mod/server.h +++ b/src/mod/server.mod/server.h @@ -154,15 +154,4 @@ enum { NETT_OTHER /* Others */ }; -/* Available sasl mechanisms. */ -enum { - SASL_MECHANISM_PLAIN, - SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE, - SASL_MECHANISM_EXTERNAL, - SASL_MECHANISM_NUM -}; - -/* Must be extern to avoid odr-violation and allow make static */ -extern char const *SASL_MECHANISMS[]; - #endif /* _EGG_MOD_SERVER_SERVER_H */ diff --git a/src/mod/server.mod/servmsg.c b/src/mod/server.mod/servmsg.c index c0e048a2b..25adf8924 100644 --- a/src/mod/server.mod/servmsg.c +++ b/src/mod/server.mod/servmsg.c @@ -20,15 +20,12 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#undef answer /* before resolv.h because it could collide with src/mod/module.h - * (dietlibc) */ -#include /* base64 encode b64_ntop() and base64 decode b64_pton() */ -#include #ifdef TLS #include #endif #include "../irc.mod/irc.h" #include "../channels.mod/channels.h" +#include #include "server.h" char *encode_msgtags(Tcl_Obj *msgtagdict); @@ -47,6 +44,9 @@ static void monitor_clear(); int account_notify = 1, extended_join = 1, account_tag = 0; Tcl_Obj *ncapeslist; +extern int sasl; +extern int sasl_authenticate_initial(const struct cap_values *); + /* We try to change to a preferred unique nick here. We always first try the * specified alternate nick. If that fails, we repeatedly modify the nick * until it gets accepted. @@ -1334,195 +1334,6 @@ static int gotsetname(char *from, char *msg) return 0; } -static int tryauthenticate(char *from, char *msg) -{ - char src[(sizeof sasl_username) + (sizeof sasl_username) + - (sizeof sasl_password)] = ""; - char *s; - /* 400-byte chunk, see: https://ircv3.net/specs/extensions/sasl-3.1.html - * base64 padding */ - #ifndef MAX - #define MAX(a,b) (((a)>(b))?(a):(b)) - #endif - unsigned char dst[((MAX((sizeof src), 400) + 2) / 3) << 2] = ""; -#ifdef HAVE_EVP_PKEY_GET1_EC_KEY - int olen; - FILE *fp; - EVP_PKEY *pkey; - unsigned char *sig; -#if OPENSSL_VERSION_NUMBER >= 0x10000000L /* 1.0.0 */ - EVP_PKEY_CTX *ctx; - size_t siglen; -#else - EC_KEY *eckey; - int ret; - unsigned int siglen; -#endif /* OPENSSL_VERSION_NUMBER >= 0x10000000L */ -#endif /* HAVE_EVP_PKEY_GET1_EC_KEY */ - putlog(LOG_DEBUG, "*", "SASL: got AUTHENTICATE %s", msg); - if (msg[0] == '+') { - s = src; - /* Don't use snprintf due to \0 inside */ - if (sasl_mechanism == SASL_MECHANISM_PLAIN) { - strcpy(s, sasl_username); - s += strlen(sasl_username) + 1; - strcpy(s, sasl_username); - s += strlen(sasl_username) + 1; - strcpy(s, sasl_password); - s += strlen(sasl_password); - dst[0] = 0; - if (b64_ntop((unsigned char *) src, s - src, (char *) dst, sizeof dst) == -1) { - putlog(LOG_SERV, "*", "SASL: AUTHENTICATE error: could not base64 " - "encode"); - /* TODO: send cap end for all error cases in this function ? */ - return 1; - } - /* TODO: what about olen we used for mbedtls_base64_encode() ? */ - } - else if (sasl_mechanism == SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE) { -#ifdef HAVE_EVP_PKEY_GET1_EC_KEY - strcpy(s, sasl_username); - s += strlen(sasl_username) + 1; - strcpy(s, sasl_username); - s += strlen(sasl_username); - if (b64_ntop((unsigned char *) src, s - src, (char *) dst, sizeof dst) == -1) { - putlog(LOG_SERV, "*", "SASL: AUTHENTICATE error: could not base64 " - "encode"); - return 1; - } - } -#else - putlog(LOG_DEBUG, "*", "SASL: TLS libs not present or missing EC support." - " Try the PLAIN or EXTERNAL method instead"); - return 1; - } -#endif - else { /* sasl_mechanism == SASL_MECHANISM_EXTERNAL */ -#ifdef TLS /* TLS required for EXTERNAL sasl */ - dst[0] = '+'; - dst[1] = 0; - } -#else - putlog(LOG_DEBUG, "*", "SASL: TLS libs required for EXTERNAL but are not " - "installed, try PLAIN method"); - return 1; - } -#endif /* TLS */ - putlog(LOG_DEBUG, "*", "SASL: put AUTHENTICATE %s", dst); - dprintf(DP_MODE, "AUTHENTICATE %s\n", dst); - } else { /* Only EC-challenges get extra auth messages w/o a + */ -#ifdef TLS -#ifdef HAVE_EVP_PKEY_GET1_EC_KEY - putlog(LOG_DEBUG, "*", "SASL: got AUTHENTICATE Challenge"); - olen = b64_pton(msg, dst, sizeof dst); - if (olen == -1) { - putlog(LOG_SERV, "*", "SASL: AUTHENTICATE error: could not base64 decode " - "line from server"); - return 1; - } - fp = fopen(sasl_ecdsa_key, "r"); - if (!fp) { - putlog(LOG_SERV, "*", "SASL: AUTHENTICATE error: could not open file " - "sasl_ecdsa_key %s: %s\n", sasl_ecdsa_key, strerror(errno)); - return 1; - } - pkey = PEM_read_PrivateKey(fp, NULL, 0, NULL); - if (!pkey) { - putlog(LOG_SERV, "*", "SASL: AUTHENTICATE: PEM_read_PrivateKey(): SSL " - "error = %s\n", ERR_error_string(ERR_get_error(), 0)); - fclose(fp); - return 1; - } - fclose(fp); -#if OPENSSL_VERSION_NUMBER >= 0x10000000L /* 1.0.0 */ - /* The EVP interface to digital signatures should almost always be used in - * preference to the low level interfaces. */ - if (!(ctx = EVP_PKEY_CTX_new(pkey, NULL))) { - putlog(LOG_SERV, "*", "SASL: AUTHENTICATE: EVP_PKEY_CTX_new(): SSL error = %s\n", - ERR_error_string(ERR_get_error(), 0)); - return 1; - } - EVP_PKEY_free(pkey); - if (EVP_PKEY_sign_init(ctx) <= 0) { - putlog(LOG_SERV, "*", "SASL: AUTHENTICATE: EVP_PKEY_sign_init(): SSL error = %s\n", - ERR_error_string(ERR_get_error(), 0)); - EVP_PKEY_CTX_free(ctx); - return 1; - - } - if (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0) { - putlog(LOG_SERV, "*", "SASL: AUTHENTICATE: EVP_PKEY_CTX_set_signature_md(): SSL error = %s\n", - ERR_error_string(ERR_get_error(), 0)); - EVP_PKEY_CTX_free(ctx); - return 1; - } - /* EVP_PKEY_sign() must be used instead of EVP_DigestSign*() and EVP_Sign*(), - * because EVP_PKEY_sign() does not hash the data to be signed. - * EVP_PKEY_sign() is for signing digests, EVP_DigestSign*() and EVP_Sign*() - * are for signing messages. - */ - if (EVP_PKEY_sign(ctx, NULL, &siglen, dst, olen) <= 0) { - putlog(LOG_SERV, "*", "SASL: AUTHENTICATE: EVP_PKEY_sign(): SSL error = %s\n", - ERR_error_string(ERR_get_error(), 0)); - EVP_PKEY_CTX_free(ctx); - return 1; - } - sig = nmalloc(siglen); - if (EVP_PKEY_sign(ctx, sig, &siglen, dst, olen) <= 0) { - putlog(LOG_SERV, "*", "SASL: AUTHENTICATE: EVP_PKEY_sign(): SSL error = %s\n", - ERR_error_string(ERR_get_error(), 0)); - nfree(sig); - EVP_PKEY_CTX_free(ctx); - return 1; - } - EVP_PKEY_CTX_free(ctx); -#else - eckey = EVP_PKEY_get1_EC_KEY(pkey); - EVP_PKEY_free(pkey); - if (!eckey) { - putlog(LOG_SERV, "*", "SASL: AUTHENTICATE: EVP_PKEY_get1_EC_KEY(): SSL error = %s\n", - ERR_error_string(ERR_get_error(), 0)); - return 1; - } - sig = nmalloc(ECDSA_size(eckey)); - ret = ECDSA_sign(0, dst, olen, sig, &siglen, eckey); - EC_KEY_free(eckey); - if (!ret) { - putlog(LOG_SERV, "*", "SASL: AUTHENTICATE: ECDSA_sign() SSL error = %s\n", - ERR_error_string(ERR_get_error(), 0)); - nfree(sig); - return 1; - } -#endif /* OPENSSL_VERSION_NUMBER >= 0x10000000L */ - if (b64_ntop(sig, siglen, (char *) dst, sizeof dst) == -1) { - putlog(LOG_SERV, "*", "SASL: AUTHENTICATE error: could not base64 encode"); - nfree(sig); - return 1; - } - nfree(sig); - putlog(LOG_DEBUG, "*", "SASL: put AUTHENTICATE Response %s", dst); - dprintf(DP_MODE, "AUTHENTICATE %s\n", dst); -#endif /* HAVE_EVP_PKEY_GET1_EC_KEY */ -#else /* TLS */ - putlog(LOG_SERV, "*", "SASL: Received EC message, but no TLS EC libs " - "present. Try PLAIN method"); - return 1; -#endif /* TLS */ - } - return 0; -} - -static int gotauthenticate(char *from, char *msg) -{ - fixcolon(msg); /* Because Inspircd does its own thing */ - if (tryauthenticate(from, msg) && !sasl_continue) { - putlog(LOG_SERV, "*", "SASL: Aborting connection and retrying"); - nuke_server("Quitting..."); - return 1; - } - return 0; -} - /* Got 900: RPL_LOGGEDIN, users account name is set (whether by SASL or otherwise) */ static int got900(char *from, char *msg) { @@ -1534,75 +1345,8 @@ static int got900(char *from, char *msg) return 0; } -/* Got 901: RPL_LOGGEDOUT, users account name is unset (whether by SASL or otherwise) */ -static int got901(char *from, char *msg) -{ - newsplit(&msg); /* nick */ - newsplit(&msg); /* nick!ident@host */ - fixcolon(msg); - putlog(LOG_SERV, "*", "%s: %s", from, msg); - return 0; -} - -static int sasl_error(char *msg) -{ - putlog(LOG_SERV, "*", "SASL: %s", msg); - dprintf(DP_MODE, "CAP END\n"); - sasl_timeout_time = 0; - if (!sasl_continue) { - putlog(LOG_DEBUG, "*", "SASL: Aborting connection and retrying"); - nuke_server("Quitting..."); - } - return 1; -} - -/* Got 902: ERR_NICKLOCKED, authentication fails b/c nick is unavailable - * Got 904: ERR_SASLFAIL, invalid credentials (or something not covered) - * Got 905: ERR_SASLTOOLONG, AUTHENTICATE command was too long (>400 bytes) - * Got 906: ERR_SASL_ABORTED, sent AUTHENTICATE command with * as parameter - * For easy grepping, this covers got902 got904 got905 got906 - */ -static int gotsasl90X(char *from, char *msg) -{ - newsplit(&msg); /* nick */ - fixcolon(msg); - return sasl_error(msg); -} - -/* Got 903: RPL_SASLSUCCESS, authentication successful */ -static int got903(char *from, char *msg) -{ - newsplit(&msg); /* nick */ - fixcolon(msg); - putlog(LOG_SERV, "*", "SASL: %s", msg); - dprintf(DP_MODE, "CAP END\n"); - sasl_timeout_time = 0; - return 0; -} - -/* Got 907: ERR_SASLALREADY, already authenticated */ -static int got907(char *from, char *msg) -{ - putlog(LOG_SERV, "*", "SASL: Already authenticated"); - return 0; -} - -/* Got 908: RPL_SASLMECHS, mechanisms supported by network */ -static int got908(char *from, char *msg) -{ - newsplit(&msg); /* nick */ - fixcolon(msg); - putlog(LOG_SERV, "*", "SASL: %s", msg); - return 0; -} - -static int handle_sasl_timeout() -{ - return sasl_error("timeout"); -} - /* - * 465 ERR_YOUREBANNEDCREEP :You are banned from this server + * 465 ERR_YOUREBANNEDCREEP :You are banned from this server */ static int got465(char *from, char *msg) { @@ -1768,9 +1512,11 @@ static int add_capabilities(char *msg) { } /* Helper function to see if given value exists for a capability */ -static int checkvalue(struct cap_values *caplist, const char *name) { - struct cap_values *current = caplist; +static int is_cap_value(const struct cap_values *cap_value_list, const char *name) { + const struct cap_values *current = cap_value_list; + if (!cap_value_list) + return 1; while (current != NULL) { if (!strcmp(name, current->name)) { return 1; @@ -1807,25 +1553,26 @@ static int gotcap(char *from, char *msg) { return 0; } current = cap; -/* CAP is supported, yay! If it is supported, lets load what we want to request */ + /* CAP is supported, yay! If it is supported, lets load what we want to request */ while (current != NULL) { - if (!strcmp(current->name, "sasl") && (sasl) && !(current->enabled)) { - add_req(current->name); - } else if (!strcmp(current->name, "account-notify") && (account_notify) - && (!current->enabled)) { - add_req(current->name); - } else if (!strcmp(current->name, "account-tag") && (account_tag) - && (!current->enabled)) { - add_req(current->name); - } else if (!strcmp(current->name, "extended-join") && (extended_join) - && (!current->enabled)) { - add_req(current->name); - } else if (!strcmp(current->name, "invite-notify") && (invite_notify) - && (!current->enabled)) { - add_req(current->name); - } else if (!strcmp(current->name, "message-tags") && (message_tags) - && (!current->enabled)) { - add_req(current->name); + if (!strcmp(current->name, "sasl")) { + if (sasl && !(current->enabled)) + add_req(current->name); + } else if (!strcmp(current->name, "account-notify")) { + if ((account_notify) && (!current->enabled)) + add_req(current->name); + } else if (!strcmp(current->name, "account-tag")) { + if ((account_tag) && (!current->enabled)) + add_req(current->name); + } else if (!strcmp(current->name, "extended-join")) { + if ((extended_join) && (!current->enabled)) + add_req(current->name); + } else if (!strcmp(current->name, "invite-notify")) { + if ((invite_notify) && (!current->enabled)) + add_req(current->name); + } else if (!strcmp(current->name, "message-tags")) { + if ((message_tags) && (!current->enabled)) + add_req(current->name); } /* Add any custom capes the user listed */ strlcpy(cape, cap_request, sizeof cape); @@ -1888,7 +1635,6 @@ static int gotcap(char *from, char *msg) { splitstr = strtok(NULL, " "); } } else if (!strcmp(cmd, "ACK")) { - buf[0] = 0; splitstr = strtok(msg, " "); while (splitstr != NULL) { current = cap; @@ -1903,35 +1649,9 @@ static int gotcap(char *from, char *msg) { current->enabled = 0; } else { current->enabled = 1; - } - - if ((sasl) && (!strcasecmp(current->name, "sasl")) && (current->enabled)) { - putlog(LOG_DEBUG, "*", "SASL: Starting authentication process"); - if (current->value && !checkvalue(current->value, SASL_MECHANISMS[sasl_mechanism])) { - snprintf(buf, sizeof buf, - "%s authentication method not supported", - SASL_MECHANISMS[sasl_mechanism]); - return sasl_error(buf); - } -#ifndef HAVE_EVP_PKEY_GET1_EC_KEY - if (sasl_mechanism != SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE) { -#endif - putlog(LOG_DEBUG, "*", "SASL: AUTHENTICATE %s", - SASL_MECHANISMS[sasl_mechanism]); - dprintf(DP_MODE, "AUTHENTICATE %s\n", SASL_MECHANISMS[sasl_mechanism]); - sasl_timeout_time = sasl_timeout; -#ifndef HAVE_EVP_PKEY_GET1_EC_KEY - } else { -#ifdef TLS - return sasl_error("TLS libs missing EC support, try PLAIN or EXTERNAL method, aborting authentication"); - } -#else /* TLS */ - if (sasl_mechanism != SASL_MECHANISM_PLAIN) { - return sasl_error("TLS libs not present, try PLAIN method, aborting authentication"); - } - } -#endif /* TLS */ -#endif /* HAVE_EVP_PKEY */ + if (sasl && !strcasecmp(current->name, "sasl") && + sasl_authenticate_initial(current->value)) + return 1; } } current = current->next; @@ -1945,6 +1665,7 @@ static int gotcap(char *from, char *msg) { dprintf(DP_MODE, "CAP END\n"); } current = cap; + buf[0] = 0; while (current != NULL) { if (current->enabled) { written += snprintf(buf + written, sizeof buf - written, " %s", current->name); @@ -2169,21 +1890,12 @@ static cmd_t my_raw_binds[] = { {"733", "", (IntFunc) got733, NULL}, {"734", "", (IntFunc) got734, NULL}, {"900", "", (IntFunc) got900, NULL}, - {"901", "", (IntFunc) got901, NULL}, - {"902", "", (IntFunc) gotsasl90X, NULL}, - {"903", "", (IntFunc) got903, NULL}, - {"904", "", (IntFunc) gotsasl90X, NULL}, - {"905", "", (IntFunc) gotsasl90X, NULL}, - {"906", "", (IntFunc) gotsasl90X, NULL}, - {"907", "", (IntFunc) got907, NULL}, - {"908", "", (IntFunc) got908, NULL}, {"NICK", "", (IntFunc) gotnick, NULL}, {"ERROR", "", (IntFunc) goterror, NULL}, /* ircu2.10.10 has a bug when a client is throttled ERROR is sent wrong */ {"ERROR:", "", (IntFunc) goterror, NULL}, {"KICK", "", (IntFunc) gotkick, NULL}, {"CAP", "", (IntFunc) gotcap, NULL}, - {"AUTHENTICATE", "", (IntFunc) gotauthenticate, NULL}, {"SETNAME", "", (IntFunc) gotsetname, NULL}, {NULL, NULL, NULL, NULL} };