Add SASL authentication support (EXTERNAL, PLAIN)

This commit is contained in:
Loïc Gomez 2022-01-02 14:16:10 +01:00 committed by Pierre-Louis Bonicoli
parent 1df884545f
commit dc43d75d1f
Signed by: pilou
GPG Key ID: 06914C4A5EDAA6DD
12 changed files with 365 additions and 7 deletions

View File

@ -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/)

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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;
}

View File

@ -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); }

177
src/irc.c
View File

@ -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

View File

@ -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 */

View File

@ -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; }
\"[^"]*\" {

94
src/utils/base64.c Normal file
View File

@ -0,0 +1,94 @@
/*
* Base64 encoding/decoding (RFC1341)
* Copyright (c) 2005-2011, Jouni Malinen <j@w1.fi>
* Copyright (c) 2022 Loïc Gomez
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#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;
}

16
src/utils/base64.h Normal file
View File

@ -0,0 +1,16 @@
/*
* Base64 encoding/decoding (RFC1341)
* Copyright (c) 2005, Jouni Malinen <j@w1.fi>
* 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 */