From dc43d75d1f7e7c01d943f085120f704d2dac831d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Gomez?= Date: Sun, 2 Jan 2022 14:16:10 +0100 Subject: [PATCH] Add SASL authentication support (EXTERNAL, PLAIN) --- AUTHORS | 1 + bip.conf.5 | 16 +++- samples/bip.conf | 5 ++ samples/bip.vim | 3 +- src/Makefile.am | 3 +- src/bip.c | 36 ++++++++- src/conf.y | 8 +- src/irc.c | 177 ++++++++++++++++++++++++++++++++++++++++++++- src/irc.h | 9 ++- src/lex.l | 4 + src/utils/base64.c | 94 ++++++++++++++++++++++++ src/utils/base64.h | 16 ++++ 12 files changed, 365 insertions(+), 7 deletions(-) create mode 100644 src/utils/base64.c create mode 100644 src/utils/base64.h diff --git a/AUTHORS b/AUTHORS index c044fe2..affc47e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,3 +10,4 @@ Thanks to our marketting and management team: ack|, ato, blackmore, lafouine, Gaston & gromit Crypto shamelessly stolen from Christophe 'sexy' Devine. +Credits to Jouni Malinen for base64 library (http://web.mit.edu/freebsd/head/contrib/wpa/src/utils/) diff --git a/bip.conf.5 b/bip.conf.5 index 8da8e50..147bd95 100644 --- a/bip.conf.5 +++ b/bip.conf.5 @@ -1,4 +1,4 @@ -.TH BIP.CONF 5 "17 October 2021" +.TH BIP.CONF 5 "2 January 2022" .SH NAME @@ -402,6 +402,20 @@ BIP will send that string as the realname part (description in whois result) upon connect. If not specified and if \fBdefault_realname\fP is specified in the \fBuser\fP section, BIP will use that default realname string. +.TP +\fBsasl_mechanism\fP +Tells BIP to use specified SASL mechanism. Currently supported: PLAIN, EXTERNAL. +PLAIN mechanism requires \fBsasl_username\fP and \fBsasl_password\fP and is the +default if these are set. + +.TP +\fBsasl_username\fP +This connection's username to pass on using SASL authentication. + +.TP +\fBsasl_password\fP +This connection's password to pass on using SASL authentication. + .TP \fBsource_port\fP If specified, tells BIP to connect from this port to the IRC server. diff --git a/samples/bip.conf b/samples/bip.conf index 97c8a52..37af202 100644 --- a/samples/bip.conf +++ b/samples/bip.conf @@ -240,6 +240,11 @@ #You can specify this field more than once. BIP will send the text as is to the server. #on_connect_send = "PRIVMSG NickServ :IDENTIFY nspassword"; + # You can connect with SASL on networks supporting it + #sasl_username = "username"; + #sasl_password = "sikioure password"; + #sasl_mechanism = "PLAIN"; + # Some options: #away_nick = "bip`away"; # Away message to be set when no client is connected diff --git a/samples/bip.vim b/samples/bip.vim index 7b2585e..4f30b66 100644 --- a/samples/bip.vim +++ b/samples/bip.vim @@ -91,7 +91,8 @@ syn keyword bipCoKeyword contained nextgroup=bipBoolV autojoin_on_kick \ follow_nick ignore_first_nick log ignore_server_capab syn keyword bipCoKeyword contained nextgroup=bipStringV name user nick \ network password vhost away_nick on_connect_send realname - \ no_client_away_msg ssl_check_mode + \ no_client_away_msg ssl_check_mode sasl_username sasl_password + \ sasl_mechanism syn keyword bipCoKeyword contained nextgroup=bipNumericV source_port " Channel elements (lvl 2) diff --git a/src/Makefile.am b/src/Makefile.am index d6e3e86..7489033 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -10,7 +10,8 @@ libbip_a_SOURCES = conf.y lex.l \ md5.c md5.h \ path_util.c path_util.h \ tuple.h \ - util.c util.h + util.c util.h \ + utils/base64.c utils/base64.h libbip_a_CFLAGS = ${OPENSSL_CFLAGS} $(AM_CFLAGS) diff --git a/src/bip.c b/src/bip.c index 3ca2d80..4f073a4 100644 --- a/src/bip.c +++ b/src/bip.c @@ -2,7 +2,8 @@ * $Id: bip.c,v 1.39 2005/04/21 06:58:50 nohar Exp $ * * This file is part of the bip project - * Copyright (C) 2004 2005 Arnaud Cornet and Loïc Gomez + * Copyright (C) 2004,2005 Arnaud Cornet + * Copyright (C) 2004,2005,2022 Loïc Gomez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -480,6 +481,22 @@ static int add_connection(bip_t *bip, struct bipuser *user, list_t *data) case LEX_PASSWORD: MOVE_STRING(l->s_password, t->pdata); break; + case LEX_SASL_USERNAME: + MOVE_STRING(l->sasl_username, t->pdata); + break; + case LEX_SASL_PASSWORD: + MOVE_STRING(l->sasl_password, t->pdata); + break; + case LEX_SASL_MECHANISM: + if (strcmp(t->pdata, "PLAIN") == 0) { + l->sasl_mechanism = SASL_AUTH_PLAIN; + } else if (strcmp(t->pdata, "EXTERNAL") == 0) { + l->sasl_mechanism = SASL_AUTH_EXTERNAL; + } else { + conf_die(bip, "Unsupported SASL mechanism %s.", t->pdata); + return 0; + } + break; case LEX_VHOST: MOVE_STRING(l->vhost, t->pdata); break; @@ -595,6 +612,23 @@ static int add_connection(bip_t *bip, struct bipuser *user, list_t *data) l->realname = bip_strdup(user->default_realname); } + if (l->sasl_username && !l->sasl_password) { + conf_die(bip, "sasl_username set without sasl_password."); + return 0; + } + + if (!l->sasl_username && l->sasl_password) { + conf_die(bip, "sasl_password set without sasl_username."); + return 0; + } + + if (l->sasl_mechanism == SASL_AUTH_PLAIN && (!l->sasl_username || !l->sasl_password)) { + conf_die(bip, "SASL mechanism PLAIN requires username and password."); + return 0; + } + if (l->sasl_username && !l->sasl_mechanism) + l->sasl_mechanism = SASL_AUTH_PLAIN; + l->in_use = 1; return 1; } diff --git a/src/conf.y b/src/conf.y index 3cf56cb..ca735ee 100644 --- a/src/conf.y +++ b/src/conf.y @@ -68,7 +68,7 @@ struct tuple *tuple_l_new(int type, void *p) %} -%token LEX_IP LEX_EQ LEX_PORT LEX_CSS LEX_SEMICOLON LEX_CONNECTION LEX_NETWORK LEX_LBRA LEX_RBRA LEX_USER LEX_NAME LEX_NICK LEX_SERVER LEX_PASSWORD LEX_SRCIP LEX_HOST LEX_VHOST LEX_SOURCE_PORT LEX_NONE LEX_COMMENT LEX_BUNCH LEX_REALNAME LEX_SSL LEX_SSL_CHECK_MODE LEX_SSL_CHECK_STORE LEX_SSL_CLIENT_CERTFILE LEX_CIPHERS LEX_CSS_CIPHERS LEX_DEFAULT_CIPHERS LEX_DH_PARAM LEX_CHANNEL LEX_KEY LEX_LOG_ROOT LEX_LOG_FORMAT LEX_LOG_LEVEL LEX_BACKLOG_LINES LEX_BACKLOG_TIMESTAMP LEX_BACKLOG_NO_TIMESTAMP LEX_BACKLOG LEX_LOG LEX_LOG_SYSTEM LEX_LOG_SYNC_INTERVAL LEX_FOLLOW_NICK LEX_ON_CONNECT_SEND LEX_AWAY_NICK LEX_PID_FILE LEX_WRITE_OIDENTD LEX_OIDENTD_FILE LEX_IGN_FIRST_NICK LEX_ALWAYS_BACKLOG LEX_BLRESET_ON_TALK LEX_BLRESET_CONNECTION LEX_DEFAULT_USER LEX_DEFAULT_NICK LEX_DEFAULT_REALNAME LEX_NO_CLIENT_AWAY_MSG LEX_BL_MSG_ONLY LEX_ADMIN LEX_BIP_USE_NOTICE LEX_CSS_PEM LEX_AUTOJOIN_ON_KICK LEX_IGNORE_CAPAB LEX_RECONN_TIMER +%token LEX_IP LEX_EQ LEX_PORT LEX_CSS LEX_SEMICOLON LEX_CONNECTION LEX_NETWORK LEX_LBRA LEX_RBRA LEX_USER LEX_NAME LEX_NICK LEX_SERVER LEX_PASSWORD LEX_SRCIP LEX_HOST LEX_VHOST LEX_SOURCE_PORT LEX_NONE LEX_COMMENT LEX_BUNCH LEX_REALNAME LEX_SSL LEX_SSL_CHECK_MODE LEX_SSL_CHECK_STORE LEX_SSL_CLIENT_CERTFILE LEX_CIPHERS LEX_CSS_CIPHERS LEX_DEFAULT_CIPHERS LEX_DH_PARAM LEX_CHANNEL LEX_KEY LEX_LOG_ROOT LEX_LOG_FORMAT LEX_LOG_LEVEL LEX_BACKLOG_LINES LEX_BACKLOG_TIMESTAMP LEX_BACKLOG_NO_TIMESTAMP LEX_BACKLOG LEX_LOG LEX_LOG_SYSTEM LEX_LOG_SYNC_INTERVAL LEX_FOLLOW_NICK LEX_ON_CONNECT_SEND LEX_AWAY_NICK LEX_PID_FILE LEX_WRITE_OIDENTD LEX_OIDENTD_FILE LEX_IGN_FIRST_NICK LEX_ALWAYS_BACKLOG LEX_BLRESET_ON_TALK LEX_BLRESET_CONNECTION LEX_DEFAULT_USER LEX_DEFAULT_NICK LEX_DEFAULT_REALNAME LEX_NO_CLIENT_AWAY_MSG LEX_BL_MSG_ONLY LEX_ADMIN LEX_BIP_USE_NOTICE LEX_CSS_PEM LEX_AUTOJOIN_ON_KICK LEX_IGNORE_CAPAB LEX_RECONN_TIMER LEX_SASL_USERNAME LEX_SASL_PASSWORD LEX_SASL_MECHANISM %union { int number; @@ -208,6 +208,12 @@ con_command: $3); } | LEX_PASSWORD LEX_EQ LEX_STRING { $$ = tuple_s_new(LEX_PASSWORD, $3); } + | LEX_SASL_USERNAME LEX_EQ LEX_STRING { $$ = tuple_s_new( + LEX_SASL_USERNAME, $3); } + | LEX_SASL_PASSWORD LEX_EQ LEX_STRING { $$ = tuple_s_new( + LEX_SASL_PASSWORD, $3); } + | LEX_SASL_MECHANISM LEX_EQ LEX_STRING { $$ = tuple_s_new( + LEX_SASL_MECHANISM, $3); } | LEX_VHOST LEX_EQ LEX_STRING { $$ = tuple_s_new(LEX_VHOST, $3); } | LEX_SOURCE_PORT LEX_EQ LEX_INT { $$ = tuple_i_new(LEX_SOURCE_PORT, $3); } diff --git a/src/irc.c b/src/irc.c index e9aa884..8ad6ba1 100644 --- a/src/irc.c +++ b/src/irc.c @@ -2,7 +2,8 @@ * $Id: irc.c,v 1.156 2005/04/21 06:58:50 nohar Exp $ * * This file is part of the bip project - * Copyright (C) 2004 2005 Arnaud Cornet and Loïc Gomez + * Copyright (C) 2004,2005 Arnaud Cornet + * Copyright (C) 2004,2005,2022 Loïc Gomez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +22,7 @@ #include "log.h" #include "connection.h" #include "md5.h" +#include "utils/base64.h" #define S_CONN_DELAY (10) @@ -67,6 +69,8 @@ static void irc_copy_cli(struct link_client *src, struct link_client *dest, static void irc_cli_make_join(struct link_client *ic); static void server_setup_reconnect_timer(struct link *link); int irc_cli_bip(bip_t *bip, struct link_client *ic, struct line *line); +static int irc_server_sasl_authenticate(struct link_server *ircs); +static char *sasl_mechanism_to_text(int sasl_mechanism); #define LAGOUT_TIME 480 #define LAGCHECK_TIME (90) @@ -386,6 +390,92 @@ int irc_dispatch_server(bip_t *bip, struct link_server *server, } ret = OK_FORGET; } + } else if (irc_line_elem_equals(line, 0, "CAP")) { + if (LINK(server)->sasl_mechanism) { + if (irc_line_elem_equals(line, 2, "ACK") && irc_line_elem_equals(line, 3, "sasl")) { + // Server is answering our CAP REQ :sasl and is SASL capable + char *sasl_mech = sasl_mechanism_to_text(LINK(server)->sasl_mechanism); + mylog(LOG_INFO, "[%s] Server is SASL capable, starting %s authentication.", + LINK(server)->name, sasl_mech); + WRITE_LINE1(CONN(server), NULL, "AUTHENTICATE", sasl_mech); + ret = OK_FORGET; + } else if (irc_line_elem_equals(line, 2, "NAK") && irc_line_elem_equals(line, 3, "sasl")) { + // Server is answering our CAP REQ :sasl and isn't SASL capable + mylog(LOG_INFO, "[%s] Server is not SASL capable.", LINK(server)->name); + ret = ERR_PROTOCOL; + } else { + // Unhandled CAP message + mylog(LOG_ERROR, "[%s] Unhandled CAP message: %s", + LINK(server)->name, irc_line_to_string(line)); + ret = OK_FORGET; + } + } else { + // Unhandled CAP message + mylog(LOG_ERROR, "[%s] Unhandled CAP message: %s", LINK(server)->name, + irc_line_to_string(line)); + ret = OK_FORGET; + } + } else if (irc_line_elem_equals(line, 0, "AUTHENTICATE")) { + if (LINK(server)->sasl_mechanism) { + if (irc_line_count(line) == 2 && irc_line_elem_equals(line, 1, "+")) { + // Server is waiting for us to authenticate, let's do it + mylog(LOG_INFO, "[%s] Server accepted our authentication mechanism.", + LINK(server)->name); + ret = irc_server_sasl_authenticate(server); + } else { + // Anything else than "AUTHENTICATE +" is unknown to us + mylog(LOG_ERROR, "[%s] Server sent gibberish: %s", + LINK(server)->name, irc_line_to_string(line)); + ret = ERR_PROTOCOL; + } + } else { + // Unhandled AUTHENTICATE message + mylog(LOG_ERROR, "[%s] Unhandled AUTHENTICATE message: %s", + LINK(server)->name, irc_line_to_string(line)); + ret = OK_FORGET; + } + } else if (irc_line_elem_equals(line, 0, "900")) { + if (irc_line_count(line) >= 5) { + mylog(LOG_INFO, "[%s] Logged in as %s(%s): %s", LINK(server)->name, + irc_line_elem(line, 3), irc_line_elem(line, 2), irc_line_elem(line, 4)); + } else { + mylog(LOG_INFO, "[%s] Logged in: %s", LINK(server)->name, irc_line_to_string(line)); + } + ret = OK_FORGET; + } else if (irc_line_elem_equals(line, 0, "901")) { + if (irc_line_count(line) >= 4) { + mylog(LOG_INFO, "[%s] Logged out: %s", + LINK(server)->name, irc_line_elem(line, 3)); + } else { + mylog(LOG_INFO, "[%s] Logged out: %s", LINK(server)->name, irc_line_to_string(line)); + } + ret = OK_FORGET; + } else if (irc_line_elem_equals(line, 0, "902")) { + mylog(LOG_INFO, "[%s] Account unavailable: %s", + LINK(server)->name, irc_line_to_string(line)); + ret = OK_FORGET; + } else if (irc_line_elem_equals(line, 0, "903")) { + mylog(LOG_INFO, "[%s] SASL authentication successful", LINK(server)->name); + WRITE_LINE1(CONN(server), NULL, "CAP", "END"); + ret = OK_FORGET; + } else if (irc_line_elem_equals(line, 0, "904")) { + mylog(LOG_ERROR, "[%s] SASL authentication failed", LINK(server)->name); + ret = ERR_AUTH; + } else if (irc_line_elem_equals(line, 0, "905")) { + mylog(LOG_ERROR, "[%s] SASL message too long", LINK(server)->name); + ret = ERR_AUTH; + } else if (irc_line_elem_equals(line, 0, "906")) { + mylog(LOG_ERROR, "[%s] SASL authentication aborted by client", + LINK(server)->name); + ret = ERR_AUTH; + } else if (irc_line_elem_equals(line, 0, "907")) { + mylog(LOG_ERROR, "[%s] SASL authentication has already been completed", + LINK(server)->name); + ret = OK_FORGET; + } else if (irc_line_elem_equals(line, 0, "908")) { + mylog(LOG_ERROR, "[%s] Server only accepts following authentication mechanisms: %s", + LINK(server)->name, irc_line_elem(line, 2)); + ret = ERR_AUTH; } else if (irc_line_elem_equals(line, 0, "433")) { if (LINK(server)->s_state != IRCS_CONNECTED) { size_t nicklen = strlen(server->nick); @@ -1927,6 +2017,83 @@ fake: #endif } +static char *sasl_mechanism_to_text(int sasl_mechanism) +{ + switch (sasl_mechanism) { + case SASL_AUTH_EXTERNAL: + return "EXTERNAL"; + case SASL_AUTH_PLAIN: + return "PLAIN"; + default: + return "UNKOWN_MECHANISM"; + } +} + +// Per RFC send packets of max 400 chars at a time +#define SASL_AUTH_CHUNK_SZ 400 +static int irc_server_sasl_authenticate(struct link_server *ircs) +{ + char *sasl_username = LINK(ircs)->sasl_username; + char *sasl_password = LINK(ircs)->sasl_password; + + if (LINK(ircs)->sasl_mechanism == SASL_AUTH_EXTERNAL) { + WRITE_LINE1(CONN(ircs), NULL, "AUTHENTICATE", "+"); + return OK_FORGET; + } + + // Should not happen, but we never know right ? + if (!sasl_username || !sasl_password) { + mylog(LOG_ERROR, "[%s] Missing SASL username or password.", LINK(ircs)->name); + return ERR_AUTH; + } + + /* + * Other than EXTERNAL we only support PLAIN. + */ + + size_t chunk_chars = SASL_AUTH_CHUNK_SZ; + char chunk[SASL_AUTH_CHUNK_SZ + 1]; + size_t u_len = strlen(sasl_username); + size_t p_len = strlen(sasl_password); + size_t raw_len = u_len*2 + p_len + 2; + size_t enc_len; + unsigned char *raw_str = bip_malloc(raw_len + 1); + unsigned char *enc_str; + + memcpy(raw_str, sasl_username, u_len); + raw_str[u_len] = '\0'; + memcpy(raw_str + u_len + 1, sasl_username, u_len); + raw_str[u_len*2 + 1] = '\0'; + memcpy(raw_str + u_len*2 + 2, sasl_password, p_len); + enc_str = base64_encode(raw_str, raw_len, &enc_len); + mylog(LOG_DEBUG, "[%s] Base64 encoded SASL auth token (len %d): %s", LINK(ircs)->name, enc_len, enc_str); + + for (size_t i = 0; i < enc_len; i += chunk_chars) { + size_t remaining = enc_len - i; + if (remaining < chunk_chars) { + memcpy(chunk, &enc_str[i], remaining); + chunk[remaining]= '\0'; + } else { + memcpy(chunk, &enc_str[i], chunk_chars); + chunk[chunk_chars]= '\0'; + } + mylog(LOG_DEBUG, "[%s] SASL AUTHENTICATE chunk %d, len %d: %s", + LINK(ircs)->name, i/chunk_chars, strlen(chunk), chunk); + WRITE_LINE1(CONN(ircs), NULL, "AUTHENTICATE", chunk); + + // Send a closing AUTHENTICATE line if last chunk size was exactly 400 + if (remaining == chunk_chars) { + mylog(LOG_DEBUG, "[%s] Last SASL chunk was exactly 400, sending +", + LINK(ircs)->name); + WRITE_LINE1(CONN(ircs), NULL, "AUTHENTICATE", "+"); + break; + } + } + free(enc_str); + + return OK_FORGET; +} + static void irc_server_startup(struct link_server *ircs) { char *nick; @@ -2195,6 +2362,12 @@ connection_t *irc_server_connect(struct link *link) list_add_last(&_bip->conn_list, conn); oidentd_dump(_bip); + + if (link->sasl_mechanism) { + mylog(LOG_INFO, "[%s] SASL (%s) enabled, sending CAP REQ.", + link->name, sasl_mechanism_to_text(link->sasl_mechanism)); + WRITE_LINE2(conn, NULL, "CAP", "REQ", ":sasl"); + } irc_server_startup(ls); return conn; } @@ -2635,6 +2808,8 @@ void link_kill(bip_t *bip, struct link *link) MAYFREE(link->username); MAYFREE(link->realname); MAYFREE(link->s_password); + MAYFREE(link->sasl_username); + MAYFREE(link->sasl_password); MAYFREE(link->connect_nick); MAYFREE(link->vhost); #ifdef HAVE_LIBSSL diff --git a/src/irc.h b/src/irc.h index 334e864..1e26d72 100644 --- a/src/irc.h +++ b/src/irc.h @@ -2,7 +2,8 @@ * $Id: irc.h,v 1.43 2005/04/21 06:58:50 nohar Exp $ * * This file is part of the bip project - * Copyright (C) 2004 2005 Arnaud Cornet and Loïc Gomez + * Copyright (C) 2004,2005 Arnaud Cornet + * Copyright (C) 2004,2005,2022 Loïc Gomez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -106,6 +107,9 @@ struct network struct server *serverv; }; +#define SASL_AUTH_EXTERNAL 1 +#define SASL_AUTH_PLAIN 2 + struct link { char *name; /* id */ @@ -153,6 +157,9 @@ struct link { char *username; char *realname; char *s_password; + char *sasl_username; + char *sasl_password; + int sasl_mechanism; char *connect_nick; /* socket creation info */ diff --git a/src/lex.l b/src/lex.l index 80e68d9..1347b54 100644 --- a/src/lex.l +++ b/src/lex.l @@ -5,6 +5,7 @@ * * This file is part of the bip proproject * Copyright (C) 2004 Arnaud Cornet + * Copyright (C) 2022 Loïc Gomez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -115,6 +116,9 @@ list_t *parse_conf(FILE *file, int *err) "client_side_ssl_pem" { return LEX_CSS_PEM; } "client_side_ciphers" { return LEX_CSS_CIPHERS; } "client_side_dh_param" { return LEX_DH_PARAM; } +"sasl_username" { return LEX_SASL_USERNAME; } +"sasl_password" { return LEX_SASL_PASSWORD; } +"sasl_mechanism" { return LEX_SASL_MECHANISM; } "ignore_server_capab" { return LEX_IGNORE_CAPAB; } "reconn_timer" { return LEX_RECONN_TIMER; } \"[^"]*\" { diff --git a/src/utils/base64.c b/src/utils/base64.c new file mode 100644 index 0000000..57488ba --- /dev/null +++ b/src/utils/base64.c @@ -0,0 +1,94 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005-2011, Jouni Malinen + * Copyright (c) 2022 Loïc Gomez + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include +#include +#include +#include "base64.h" + +static const unsigned char base64_table[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * base64_encode - Base64 encode + * @src: Data to be encoded + * @len: Length of the data to be encoded + * @out_len: Pointer to output length variable, or %NULL if not used + * Returns: Allocated buffer of out_len bytes of encoded data, + * or %NULL on failure + * + * Caller is responsible for freeing the returned buffer. Returned buffer is + * nul terminated to make it easier to use as a C string. The nul terminator is + * not included in out_len. + * + * BIP change: remove line returns. + */ +unsigned char * base64_encode(const unsigned char *src, size_t len, + size_t *out_len) +{ + unsigned char *out, *pos; + const unsigned char *end, *in; + size_t olen; + int line_len; + + olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */ + olen += olen / 72; /* line feeds */ + olen++; /* nul termination */ + if (olen < len) + return NULL; /* integer overflow */ + out = malloc(olen); + if (out == NULL) + return NULL; + + end = src + len; + in = src; + pos = out; + line_len = 0; + while (end - in >= 3) { + *pos++ = base64_table[in[0] >> 2]; + *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; + *pos++ = base64_table[in[2] & 0x3f]; + in += 3; + line_len += 4; + /* + * BIP change: remove line returns. + if (line_len >= 72) { + *pos++ = '\n'; + line_len = 0; + } + */ + } + + if (end - in) { + *pos++ = base64_table[in[0] >> 2]; + if (end - in == 1) { + *pos++ = base64_table[(in[0] & 0x03) << 4]; + *pos++ = '='; + } else { + *pos++ = base64_table[((in[0] & 0x03) << 4) | + (in[1] >> 4)]; + *pos++ = base64_table[(in[1] & 0x0f) << 2]; + } + *pos++ = '='; + line_len += 4; + } + + /* + * BIP change: remove line returns. + if (line_len) + *pos++ = '\n'; + */ + + *pos = '\0'; + if (out_len) + *out_len = (size_t)(pos - out); + return out; +} + diff --git a/src/utils/base64.h b/src/utils/base64.h new file mode 100644 index 0000000..666573f --- /dev/null +++ b/src/utils/base64.h @@ -0,0 +1,16 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005, Jouni Malinen + * Copyright (c) 2022 Loïc Gomez + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef BASE64_H +#define BASE64_H + +unsigned char * base64_encode(const unsigned char *src, size_t len, + size_t *out_len); + +#endif /* BASE64_H */