From 4b5e103d9ce569f70be09aa9541bd5752319c648 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Tue, 22 Feb 2022 14:41:09 +0100 Subject: [PATCH] move password cache into libstrophe The cache is stored per connection object and is cleared on * entry of wrong password * release of connection object * successful connection It can be configured that libstrophe retries the password entry in case the user entered a wrong password. Signed-off-by: Steffen Jaeckel --- ChangeLog | 4 ++ examples/complex.c | 47 +++++------------- src/auth.c | 1 + src/common.h | 6 +++ src/conn.c | 37 +++++++++++++++ src/tls.c | 37 +++++++++++++++ src/tls.h | 3 ++ src/tls_gnutls.c | 108 +++++++++++++++++++++++++++++------------- src/tls_openssl.c | 102 ++++++++++++++++++++++++++------------- strophe.h | 19 ++++++-- tests/test_xmppaddr.c | 4 +- 11 files changed, 262 insertions(+), 106 deletions(-) diff --git a/ChangeLog b/ChangeLog index f4e48b3..b536a42 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,11 @@ 0.12.0 - Fix potential infinite loop in resolver (#200) - Add code coverage support + - Add support for password-protected TLS key & PKCS#12/PFX files - New API: + - xmpp_conn_get_keyfile() + - xmpp_conn_set_password_callback() + - xmpp_conn_set_password_retries() - xmpp_stanza_get_child_by_path() - xmpp_conn_set_sockopt_callback() - xmpp_sockopt_cb_keepalive() diff --git a/examples/complex.c b/examples/complex.c index 67c5d01..e2d23eb 100644 --- a/examples/complex.c +++ b/examples/complex.c @@ -95,47 +95,22 @@ static int certfail_handler(const xmpp_tlscert_t *cert, } static int -password_callback(char *pw, size_t pw_max, const char *fname, void *userdata) +password_callback(char *pw, size_t pw_max, xmpp_conn_t *conn, void *userdata) { (void)userdata; - /* when using an encrypted keyfile, we get asked multiple times for the - * password ... cache it until we're connected - */ - static struct { - char pass[256]; - unsigned char hash[XMPP_SHA1_DIGEST_SIZE]; - size_t passlen, fnamelen; - unsigned count; - } password_cache; - int ret = -1; - unsigned char hash[XMPP_SHA1_DIGEST_SIZE]; - - size_t fname_len = strlen(fname); - xmpp_sha1_digest((void *)fname, fname_len, hash); - if (fname_len && fname_len == password_cache.fnamelen && - memcmp(hash, password_cache.hash, sizeof(hash)) == 0) { - if (password_cache.passlen && --password_cache.count) { - memcpy(pw, password_cache.pass, password_cache.passlen + 1); - ret = password_cache.passlen; - if (!password_cache.count) - memset(&password_cache, 0, sizeof(password_cache)); - return ret; - } - } - printf("Trying to unlock %s\n", fname); + printf("Trying to unlock %s\n", xmpp_conn_get_keyfile(conn)); char *pass = getpass("Please enter password: "); if (!pass) return -1; size_t passlen = strlen(pass); - if (passlen < pw_max) { - memcpy(pw, pass, passlen + 1); - ret = passlen; - memcpy(password_cache.pass, pass, passlen + 1); - password_cache.passlen = passlen; - password_cache.count = 6; - memcpy(password_cache.hash, hash, sizeof(hash)); - password_cache.fnamelen = fname_len; + int ret; + if (passlen >= pw_max) { + ret = -1; + goto out; } + ret = passlen + 1; + memcpy(pw, pass, ret); +out: memset(pass, 0, passlen); return ret; } @@ -327,7 +302,9 @@ int main(int argc, char **argv) xmpp_conn_set_sockopt_callback(conn, sockopt_cb); /* ask for a password if key is protected */ - xmpp_conn_set_password_callback(conn, password_callback, conn); + xmpp_conn_set_password_callback(conn, password_callback, NULL); + /* try at max 3 times in case the user enters the password wrong */ + xmpp_conn_set_password_retries(conn, 3); /* setup authentication information */ if (key) { xmpp_conn_set_client_cert(conn, cert, key); diff --git a/src/auth.c b/src/auth.c index 24a6291..55c61f9 100644 --- a/src/auth.c +++ b/src/auth.c @@ -825,6 +825,7 @@ static void _auth(xmpp_conn_t *conn) static void _auth_success(xmpp_conn_t *conn) { + tls_clear_password_cache(conn); conn->authenticated = 1; /* call connection handler */ conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, conn->userdata); diff --git a/src/common.h b/src/common.h index 964fb5f..2cdd42e 100644 --- a/src/common.h +++ b/src/common.h @@ -194,6 +194,12 @@ struct _xmpp_conn_t { xmpp_certfail_handler certfail_handler; xmpp_password_callback password_callback; void *password_callback_userdata; + struct { + char pass[1024]; + unsigned char fname_hash[XMPP_SHA1_DIGEST_SIZE]; + size_t passlen, fnamelen; + } password_cache; + unsigned int password_retries; /* if server returns or we must do them */ int bind_required; diff --git a/src/conn.c b/src/conn.c index 261c3cb..e6549a3 100644 --- a/src/conn.c +++ b/src/conn.c @@ -184,6 +184,8 @@ xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t *ctx) conn->certfail_handler = NULL; conn->password_callback = NULL; conn->password_callback_userdata = NULL; + tls_clear_password_cache(conn); + conn->password_retries = 1; conn->bind_required = 0; conn->session_required = 0; @@ -384,6 +386,7 @@ int xmpp_conn_release(xmpp_conn_t *conn) strophe_free(ctx, conn->tls_cafile); if (conn->tls_capath) strophe_free(ctx, conn->tls_capath); + tls_clear_password_cache(conn); strophe_free(ctx, conn); released = 1; } @@ -511,6 +514,40 @@ void xmpp_conn_set_password_callback(xmpp_conn_t *conn, conn->password_callback_userdata = userdata; } +/** Set the number of retry attempts to decrypt a private key file. + * + * In case the user enters the password manually it can be useful to + * directly retry if the decryption of the key file failed. + * + * @param conn a Strophe connection object + * @param retries The number of retries that should be tried + * + * @ingroup TLS + */ +void xmpp_conn_set_password_retries(xmpp_conn_t *conn, unsigned int retries) +{ + if (retries == 0) + conn->password_retries = 1; + else + conn->password_retries = retries; +} + +/** Retrieve the path of the key file that shall be unlocked. + * + * This makes usually sense to be called from the + * \ref xmpp_password_callback . + * + * @param conn a Strophe connection object + * + * @return a String of the path to the key file + * + * @ingroup TLS + */ +const char *xmpp_conn_get_keyfile(const xmpp_conn_t *conn) +{ + return conn->tls_client_key; +} + /** Set the Client Certificate and Private Key or PKCS#12 encoded file that * will be bound to the connection. If any of them was previously set, it * will be discarded. This should not be used after a connection is created. diff --git a/src/tls.c b/src/tls.c index 578283d..5d78445 100644 --- a/src/tls.c +++ b/src/tls.c @@ -211,3 +211,40 @@ int tlscert_add_dnsname(xmpp_tlscert_t *cert, const char *dnsname) strophe_strdup(cert->ctx, dnsname); return 0; } + +int tls_caching_password_callback(char *pw, size_t pw_max, xmpp_conn_t *conn) +{ + int ret; + unsigned char hash[XMPP_SHA1_DIGEST_SIZE]; + + const char *fname = conn->tls_client_key; + size_t fname_len = strlen(fname); + xmpp_sha1_digest((void *)fname, fname_len, hash); + if (fname_len && fname_len == conn->password_cache.fnamelen && + memcmp(hash, conn->password_cache.fname_hash, sizeof(hash)) == 0) { + if (conn->password_cache.passlen) { + memcpy(pw, conn->password_cache.pass, + conn->password_cache.passlen + 1); + return conn->password_cache.passlen; + } + } + size_t max_len = pw_max == 256 ? pw_max : sizeof(conn->password_cache.pass); + ret = conn->password_callback(conn->password_cache.pass, max_len, conn, + conn->password_callback_userdata); + + if (ret < 0 || ret >= (ssize_t)max_len) { + memset(conn->password_cache.pass, 0, sizeof(conn->password_cache.pass)); + return -1; + } + conn->password_cache.pass[ret] = '\0'; + memcpy(pw, conn->password_cache.pass, ret + 1); + conn->password_cache.passlen = ret; + conn->password_cache.fnamelen = fname_len; + memcpy(conn->password_cache.fname_hash, hash, sizeof(hash)); + return conn->password_cache.passlen; +} + +void tls_clear_password_cache(xmpp_conn_t *conn) +{ + memset(&conn->password_cache, 0, sizeof(conn->password_cache)); +} diff --git a/src/tls.h b/src/tls.h index dc4c9f3..86e2bbc 100644 --- a/src/tls.h +++ b/src/tls.h @@ -62,4 +62,7 @@ int tls_is_recoverable(int error); xmpp_tlscert_t *tlscert_new(xmpp_ctx_t *ctx); int tlscert_add_dnsname(xmpp_tlscert_t *cert, const char *dnsname); +int tls_caching_password_callback(char *pw, size_t pw_max, xmpp_conn_t *conn); +void tls_clear_password_cache(xmpp_conn_t *conn); + #endif /* __LIBSTROPHE_TLS_H__ */ diff --git a/src/tls_gnutls.c b/src/tls_gnutls.c index 7c506f5..ac3d027 100644 --- a/src/tls_gnutls.c +++ b/src/tls_gnutls.c @@ -62,12 +62,7 @@ static int _tls_password_callback(void *userdata, UNUSED(token_url); UNUSED(token_label); UNUSED(flags); - if (!conn->password_callback) { - strophe_error(conn->ctx, "tls", "No password callback set"); - return -1; - } - int ret = conn->password_callback(pin, pin_max, conn->tls_client_key, - conn->password_callback_userdata); + int ret = tls_caching_password_callback(pin, pin_max, conn); return ret > 0 ? 0 : GNUTLS_E_PKCS11_PIN_ERROR; } @@ -93,10 +88,15 @@ error_out: static gnutls_x509_crt_t _tls_load_cert_p12(xmpp_conn_t *conn) { gnutls_pkcs12_t p12; - gnutls_x509_crt_t *cert; + gnutls_x509_crt_t *cert = NULL; gnutls_datum_t data; gnutls_x509_privkey_t key; - unsigned int cert_num = 0; + unsigned int cert_num = 0, retries = 0; + int err = -1; + if (!conn->password_callback) { + strophe_error(conn->ctx, "tls", "No password callback set"); + return NULL; + } if (gnutls_pkcs12_init(&p12) < 0) return NULL; if (gnutls_load_file(conn->tls_client_key, &data) < 0) @@ -104,18 +104,30 @@ static gnutls_x509_crt_t _tls_load_cert_p12(xmpp_conn_t *conn) if (gnutls_pkcs12_import(p12, &data, GNUTLS_X509_FMT_DER, 0) < 0) goto error_out2; - char pass[GNUTLS_PKCS11_MAX_PIN_LEN]; - pass[0] = '\0'; - int passlen = - _tls_password_callback(conn, 0, NULL, NULL, 0, pass, sizeof(pass)); - if (passlen < 0) - pass[0] = '\0'; + while (retries++ < conn->password_retries) { + char pass[GNUTLS_PKCS11_MAX_PIN_LEN]; + int passlen = + _tls_password_callback(conn, 0, NULL, NULL, 0, pass, sizeof(pass)); + if (passlen < 0) + continue; - int res = gnutls_pkcs12_simple_parse(p12, pass, &key, &cert, &cert_num, + err = gnutls_pkcs12_simple_parse(p12, pass, &key, &cert, &cert_num, NULL, NULL, NULL, 0); + memset(pass, 0, sizeof(pass)); + if (err == 0) + break; + tls_clear_password_cache(conn); + if (err != GNUTLS_E_DECRYPTION_FAILED && + err != GNUTLS_E_MAC_VERIFY_FAILED) { + strophe_error(conn->ctx, "tls", "could not read P12 file"); + break; + } + strophe_debug(conn->ctx, "tls", "wrong password?"); + } + gnutls_pkcs12_deinit(p12); gnutls_free(data.data); - if (res < 0) + if (err < 0) goto error_out; gnutls_x509_privkey_deinit(key); if (cert_num > 1) { @@ -129,6 +141,7 @@ static gnutls_x509_crt_t _tls_load_cert_p12(xmpp_conn_t *conn) error_out2: gnutls_free(data.data); error_out: + tls_clear_password_cache(conn); if (cert) { for (unsigned int n = 0; n < cert_num; ++n) { gnutls_x509_crt_deinit(cert[n]); @@ -407,31 +420,55 @@ tls_t *tls_new(xmpp_conn_t *conn) gnutls_certificate_allocate_credentials(&tls->cred); tls_set_credentials(tls, NULL); - gnutls_certificate_set_pin_function(tls->cred, _tls_password_callback, - conn); + if (conn->password_callback) + gnutls_certificate_set_pin_function(tls->cred, + _tls_password_callback, conn); if (conn->tls_client_cert && conn->tls_client_key) { + unsigned int retries = 0; tls->client_cert = _tls_load_cert(conn); if (!tls->client_cert) { strophe_error(tls->ctx, "tls", "could not read client certificate"); - gnutls_certificate_free_credentials(tls->cred); - gnutls_deinit(tls->session); - strophe_free(tls->ctx, tls); - return NULL; + goto error_out; + } + while (retries++ < conn->password_retries) { + int err = gnutls_certificate_set_x509_key_file( + tls->cred, conn->tls_client_cert, conn->tls_client_key, + GNUTLS_X509_FMT_PEM); + if (err == 0) + break; + tls_clear_password_cache(conn); + if (err != GNUTLS_E_DECRYPTION_FAILED) { + strophe_error(tls->ctx, "tls", + "could not read private key"); + goto error_out; + } + strophe_debug(tls->ctx, "tls", "wrong password?"); } - gnutls_certificate_set_x509_key_file( - tls->cred, conn->tls_client_cert, conn->tls_client_key, - GNUTLS_X509_FMT_PEM); } else if (conn->tls_client_key) { - char pass[GNUTLS_PKCS11_MAX_PIN_LEN]; - pass[0] = '\0'; - int passlen = _tls_password_callback(conn, 0, NULL, NULL, 0, pass, - sizeof(pass)); - if (passlen < 0) + unsigned int retries = 0; + + while (retries++ < conn->password_retries) { + char pass[GNUTLS_PKCS11_MAX_PIN_LEN]; pass[0] = '\0'; - gnutls_certificate_set_x509_simple_pkcs12_file( - tls->cred, conn->tls_client_key, GNUTLS_X509_FMT_DER, pass); + int passlen = _tls_password_callback(conn, 0, NULL, NULL, 0, + pass, sizeof(pass)); + if (passlen < 0) + continue; + int err = gnutls_certificate_set_x509_simple_pkcs12_file( + tls->cred, conn->tls_client_key, GNUTLS_X509_FMT_DER, pass); + memset(pass, 0, sizeof(pass)); + if (err == 0) + break; + tls_clear_password_cache(conn); + if (err != GNUTLS_E_DECRYPTION_FAILED && + err != GNUTLS_E_MAC_VERIFY_FAILED) { + strophe_error(tls->ctx, "tls", "could not read P12 file"); + goto error_out; + } + strophe_debug(tls->ctx, "tls", "wrong password?"); + } } gnutls_certificate_set_verify_function(tls->cred, _tls_verify); @@ -444,6 +481,13 @@ tls_t *tls_new(xmpp_conn_t *conn) } return tls; +error_out: + if (tls->client_cert) + gnutls_x509_crt_deinit(tls->client_cert); + gnutls_certificate_free_credentials(tls->cred); + gnutls_deinit(tls->session); + strophe_free(tls->ctx, tls); + return NULL; } void tls_free(tls_t *tls) diff --git a/src/tls_openssl.c b/src/tls_openssl.c index 4dde48b..aae0a71 100644 --- a/src/tls_openssl.c +++ b/src/tls_openssl.c @@ -541,14 +541,8 @@ static int _tls_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) static int _tls_password_callback(char *buf, int size, int rwflag, void *u) { - xmpp_conn_t *conn = u; UNUSED(rwflag); - if (!conn->password_callback) { - strophe_error(conn->ctx, "tls", "No password callback set"); - return -1; - } - return conn->password_callback(buf, (size_t)size, conn->tls_client_key, - conn->password_callback_userdata); + return tls_caching_password_callback(buf, size, u); } tls_t *tls_new(xmpp_conn_t *conn) @@ -580,10 +574,14 @@ tls_t *tls_new(xmpp_conn_t *conn) SSL_CTX_set_options(tls->ssl_ctx, SSL_OP_NO_SSLv2); /* DROWN */ SSL_CTX_set_options(tls->ssl_ctx, SSL_OP_NO_SSLv3); /* POODLE */ SSL_CTX_set_options(tls->ssl_ctx, SSL_OP_NO_TLSv1); /* BEAST */ - SSL_CTX_set_default_passwd_cb(tls->ssl_ctx, _tls_password_callback); - SSL_CTX_set_default_passwd_cb_userdata(tls->ssl_ctx, conn); + + if (conn->password_callback) { + SSL_CTX_set_default_passwd_cb(tls->ssl_ctx, _tls_password_callback); + SSL_CTX_set_default_passwd_cb_userdata(tls->ssl_ctx, conn); + } if (conn->tls_client_cert && conn->tls_client_key) { + unsigned int retries = 0; tls->client_cert = _tls_cert_read(conn); if (!tls->client_cert) { strophe_error(tls->ctx, "tls", @@ -593,8 +591,25 @@ tls_t *tls_new(xmpp_conn_t *conn) SSL_CTX_use_certificate_file(tls->ssl_ctx, conn->tls_client_cert, SSL_FILETYPE_PEM); - SSL_CTX_use_PrivateKey_file(tls->ssl_ctx, conn->tls_client_key, - SSL_FILETYPE_PEM); + while (retries++ < conn->password_retries) { + if (SSL_CTX_use_PrivateKey_file( + tls->ssl_ctx, conn->tls_client_key, SSL_FILETYPE_PEM)) { + break; + } + tls_clear_password_cache(conn); + unsigned long err = ERR_peek_error(); + if ((ERR_GET_LIB(err) == ERR_LIB_EVP && + ERR_GET_REASON(err) == EVP_R_BAD_DECRYPT) || + (ERR_GET_LIB(err) == ERR_LIB_PEM && + ERR_GET_REASON(err) == PEM_R_BAD_DECRYPT)) { + strophe_debug(tls->ctx, "tls", "wrong password?"); + continue; + } + strophe_error(tls->ctx, "tls", + "could not use private key %d %d", + ERR_GET_LIB(err), ERR_GET_REASON(err)); + goto err_free_ctx; + } } else if (conn->tls_client_key) { EVP_PKEY *pkey = NULL; STACK_OF(X509) *ca = NULL; @@ -876,7 +891,7 @@ static void _tls_log_error(xmpp_ctx_t *ctx) do { e = ERR_get_error(); if (e != 0) { - strophe_debug(ctx, "tls", "error:%08uX:%s:%s:%s", e, + strophe_debug(ctx, "tls", "error:%08X:%s:%s:%s", e, ERR_lib_error_string(e), ERR_func_error_string(e), ERR_reason_error_string(e)); } @@ -928,43 +943,66 @@ _tls_cert_read_p12(xmpp_conn_t *conn, EVP_PKEY **pkey, STACK_OF(X509) * *ca) { if (conn->tls && conn->tls->client_cert && !pkey && !ca) return conn->tls->client_cert; + X509 *cert = NULL; + PKCS12 *p12 = NULL; BIO *f = BIO_new_file(conn->tls_client_key, "r"); if (!f) { strophe_debug(conn->ctx, "tls", "f == NULL"); goto error_out; } - PKCS12 *p12 = d2i_PKCS12_bio(f, NULL); + p12 = d2i_PKCS12_bio(f, NULL); BIO_free(f); if (!p12) { strophe_debug(conn->ctx, "tls", "Could not read p12 file"); goto error_out; } - char pass[PEM_BUFSIZE]; - int passlen = _tls_password_callback(pass, sizeof(pass), 0, conn); - if (passlen <= 0) - pass[0] = '\0'; + unsigned int retries = 0; - /* For some reason `PKCS12_parse()` fails without a `EVP_PKEY` - * so if the user doesn't want it, use a local one and free it - * again directly after parsing. - */ - EVP_PKEY *pkey_; - if (!pkey) - pkey = &pkey_; - X509 *cert = NULL; - int parse_ok = PKCS12_parse(p12, pass, pkey, &cert, ca); - if (pkey == &pkey_ && pkey_) - EVP_PKEY_free(pkey_); - PKCS12_free(p12); - if (!parse_ok) { + pem_password_cb *cb = PEM_def_callback; + void *userdata = NULL; + if (conn->password_callback) { + cb = _tls_password_callback; + userdata = conn; + } + + while (retries++ < conn->password_retries) { + char pass[PEM_BUFSIZE + 1]; + int passlen = cb(pass, PEM_BUFSIZE, 0, userdata); + if (passlen < 0 || passlen > PEM_BUFSIZE) + goto error_out; + + /* For some reason `PKCS12_parse()` fails without a `EVP_PKEY` + * so if the user doesn't want it, use a local one and free it + * again directly after parsing. + */ + EVP_PKEY *pkey_; + if (!pkey) + pkey = &pkey_; + int parse_ok = PKCS12_parse(p12, pass, pkey, &cert, ca); + if (pkey == &pkey_ && pkey_) + EVP_PKEY_free(pkey_); + if (parse_ok) { + goto success; + } + cert = NULL; + tls_clear_password_cache(conn); + int err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PKCS12 && + ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE) { + strophe_debug(conn->ctx, "tls", + "Entered password is most likely wrong!"); + continue; + } strophe_debug(conn->ctx, "tls", "Could not parse PKCS#12"); goto error_out; } - return cert; error_out: _tls_log_error(conn->ctx); - return NULL; +success: + if (p12) + PKCS12_free(p12); + return cert; } static X509 *_tls_cert_read(xmpp_conn_t *conn) diff --git a/strophe.h b/strophe.h index 69bd3b4..3219e56 100644 --- a/strophe.h +++ b/strophe.h @@ -283,23 +283,30 @@ typedef int (*xmpp_certfail_handler)(const xmpp_tlscert_t *cert, * include/openssl/pem.h: #define PEM_BUFSIZE 1024 * ``` * - * The usable length for GnuTLS is 255 as it expects a NULL-terminated string. + * We expect the buffer to be NULL-terminated, therefore the usable lengths + * are: * - * The usable length for OpenSSL is 1024. + * * 255 for GnuTLS + * * 1023 for OpenSSL + * + * Useful API's inside this callback are e.g. + * + * \ref xmpp_conn_get_keyfile * * * @param pw The buffer where the password shall be stored. * @param pw_max The maximum length of the password. - * @param fname The name of the file that is going to be unlocked. + * @param conn The Strophe connection object this callback originates from. * @param userdata The userdata pointer as supplied when setting this callback. * - * @return -1 on error, else the number of bytes written to `pw` + * @return -1 on error, else the number of bytes written to `pw` w/o + * terminating NUL byte * * @ingroup TLS */ typedef int (*xmpp_password_callback)(char *pw, size_t pw_max, - const char *fname, + xmpp_conn_t *conn, void *userdata); /** The function which will be called when Strophe creates a new socket. @@ -355,6 +362,8 @@ xmpp_tlscert_t *xmpp_conn_get_peer_cert(xmpp_conn_t *const conn); void xmpp_conn_set_password_callback(xmpp_conn_t *conn, xmpp_password_callback cb, void *userdata); +void xmpp_conn_set_password_retries(xmpp_conn_t *conn, unsigned int retries); +const char *xmpp_conn_get_keyfile(const xmpp_conn_t *conn); void xmpp_conn_set_client_cert(xmpp_conn_t *conn, const char *cert, const char *key); diff --git a/tests/test_xmppaddr.c b/tests/test_xmppaddr.c index b346f89..485004f 100644 --- a/tests/test_xmppaddr.c +++ b/tests/test_xmppaddr.c @@ -19,11 +19,11 @@ #include "test.h" static int -password_callback(char *pw, size_t pw_max, const char *fname, void *userdata) +password_callback(char *pw, size_t pw_max, xmpp_conn_t *conn, void *userdata) { (void)pw_max; (void)userdata; - (void)fname; + (void)conn; memcpy(pw, "abc123", 7); return 6; }