mirror of
https://github.com/strophe/libstrophe.git
synced 2026-07-18 20:06:21 +00:00
In order to be able to load password-protected key files a password callback was added. This also adds support for PKCS#12 containers instead of certificate+key. Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
364 lines
11 KiB
C
364 lines
11 KiB
C
/* complex.c
|
|
** libstrophe XMPP client library -- more complex usage example
|
|
**
|
|
** Copyright (C) 2005-2009 Collecta, Inc.
|
|
**
|
|
** This software is provided AS-IS with no warranty, either express
|
|
** or implied.
|
|
**
|
|
** This program is dual licensed under the MIT and GPLv3 licenses.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#include <iphlpapi.h>
|
|
#include <mstcpip.h> /* tcp_keepalive */
|
|
#else
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#endif
|
|
|
|
#include <strophe.h>
|
|
|
|
/* hardcoded TCP keepalive timeout and interval */
|
|
#define KA_TIMEOUT 60
|
|
#define KA_INTERVAL 30
|
|
#define KA_COUNT 3
|
|
#define USER_TIMEOUT 150
|
|
|
|
static void print_tlscert(const xmpp_tlscert_t *cert)
|
|
{
|
|
const char *name;
|
|
size_t n;
|
|
for (n = 0; n < (unsigned)XMPP_CERT_ELEMENT_MAX; ++n) {
|
|
printf("\t%32s: %s\n", xmpp_tlscert_get_description(n),
|
|
xmpp_tlscert_get_string(cert, n));
|
|
}
|
|
n = 0;
|
|
while ((name = xmpp_tlscert_get_dnsname(cert, n++)) != NULL)
|
|
printf("\t%32s: %s\n", "dnsName", name);
|
|
printf("PEM:\n%s\n", xmpp_tlscert_get_pem(cert));
|
|
}
|
|
|
|
/* define a handler for connection events */
|
|
static void conn_handler(xmpp_conn_t *conn,
|
|
xmpp_conn_event_t status,
|
|
int error,
|
|
xmpp_stream_error_t *stream_error,
|
|
void *userdata)
|
|
{
|
|
xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata;
|
|
int secured;
|
|
|
|
(void)error;
|
|
(void)stream_error;
|
|
|
|
if (status == XMPP_CONN_CONNECT) {
|
|
fprintf(stderr, "DEBUG: connected\n");
|
|
secured = xmpp_conn_is_secured(conn);
|
|
fprintf(stderr, "DEBUG: connection is %s.\n",
|
|
secured ? "secured" : "NOT secured");
|
|
if (secured) {
|
|
xmpp_tlscert_t *cert = xmpp_conn_get_peer_cert(conn);
|
|
print_tlscert(cert);
|
|
xmpp_tlscert_free(cert);
|
|
}
|
|
xmpp_disconnect(conn);
|
|
} else {
|
|
fprintf(stderr, "DEBUG: disconnected\n");
|
|
xmpp_stop(ctx);
|
|
}
|
|
}
|
|
|
|
static int certfail_handler(const xmpp_tlscert_t *cert,
|
|
const char *const errormsg)
|
|
{
|
|
char read_char[16] = {0};
|
|
printf("Received certificate can't be validated!\n");
|
|
printf("Reason: %s\n", errormsg);
|
|
print_tlscert(cert);
|
|
printf("Do you agree to connect?\n[y(es)|n(o)]: ");
|
|
fflush(stdout);
|
|
if (fgets(read_char, sizeof(read_char), stdin) == NULL) {
|
|
printf("fgets() failed\n");
|
|
return 0;
|
|
}
|
|
printf("\n");
|
|
return read_char[0] == 'y' || read_char[0] == 'Y';
|
|
}
|
|
|
|
static int
|
|
password_callback(char *pw, size_t pw_max, const char *fname, 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);
|
|
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;
|
|
}
|
|
memset(pass, 0, passlen);
|
|
return ret;
|
|
}
|
|
|
|
static int sockopt_cb(xmpp_conn_t *conn, void *socket)
|
|
{
|
|
int timeout = KA_TIMEOUT;
|
|
int interval = KA_INTERVAL;
|
|
int count = KA_COUNT;
|
|
unsigned int user_timeout = USER_TIMEOUT;
|
|
int ret;
|
|
int optval = (timeout && interval) ? 1 : 0;
|
|
|
|
(void)conn;
|
|
|
|
#ifdef _WIN32
|
|
(void)count;
|
|
(void)user_timeout;
|
|
|
|
SOCKET sock = *((SOCKET *)socket);
|
|
struct tcp_keepalive ka;
|
|
DWORD dw = 0;
|
|
|
|
ka.onoff = optval;
|
|
ka.keepalivetime = timeout * 1000;
|
|
ka.keepaliveinterval = interval * 1000;
|
|
ret = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &ka, sizeof(ka), NULL, 0, &dw,
|
|
NULL, NULL);
|
|
#else
|
|
int sock = *((int *)socket);
|
|
|
|
fprintf(stderr, "DEBUG: setting socket options\n");
|
|
|
|
ret = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (optval) {
|
|
#ifdef TCP_KEEPIDLE
|
|
ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &timeout,
|
|
sizeof(timeout));
|
|
#elif defined(TCP_KEEPALIVE)
|
|
/* QNX receives `struct timeval' as argument, but it seems OSX does int
|
|
*/
|
|
ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, &timeout,
|
|
sizeof(timeout));
|
|
#endif /* TCP_KEEPIDLE */
|
|
if (ret < 0)
|
|
return ret;
|
|
#ifdef TCP_KEEPINTVL
|
|
ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &interval,
|
|
sizeof(interval));
|
|
if (ret < 0)
|
|
return ret;
|
|
#endif /* TCP_KEEPINTVL */
|
|
}
|
|
|
|
if (count) {
|
|
#ifdef TCP_KEEPCNT
|
|
ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count));
|
|
if (ret < 0)
|
|
return ret;
|
|
#endif /* TCP_KEEPCNT */
|
|
}
|
|
|
|
if (user_timeout) {
|
|
#ifdef TCP_USER_TIMEOUT
|
|
ret = setsockopt(sock, IPPROTO_TCP, TCP_USER_TIMEOUT, &user_timeout,
|
|
sizeof(user_timeout));
|
|
if (ret < 0)
|
|
return ret;
|
|
#elif defined(TCP_RXT_CONNDROPTIME)
|
|
int rxt = user_timeout / 1000;
|
|
ret = setsockopt(sock, IPPROTO_TCP, TCP_RXT_CONNDROPTIME, &rxt,
|
|
sizeof(rxt));
|
|
if (ret < 0)
|
|
return ret;
|
|
#endif /* TCP_USER_TIMEOUT */
|
|
}
|
|
#endif /* _WIN32 */
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void usage(int exit_code)
|
|
{
|
|
fprintf(stderr,
|
|
"Usage: complex [options] [<host> [<port>]]\n\n"
|
|
"Options:\n"
|
|
" --jid <jid> The JID to use to authenticate.\n"
|
|
" --pass <pass> The password of the JID.\n"
|
|
" --tls-cert <cert> Path to client certificate.\n"
|
|
" --tls-key <key> Path to private key or P12 file.\n\n"
|
|
" --capath <path> Path to an additional CA trust store "
|
|
"(directory).\n"
|
|
" --cafile <path> Path to an additional CA trust store "
|
|
"(single file).\n"
|
|
" --disable-tls Disable TLS.\n"
|
|
" --mandatory-tls Deny plaintext connection.\n"
|
|
" --trust-tls Trust TLS certificate.\n"
|
|
" --enable-certfail Enable certfail handler.\n"
|
|
" --legacy-ssl Use old style SSL.\n"
|
|
" --legacy-auth Allow legacy authentication.\n"
|
|
" --verbose Increase the verbosity level.\n"
|
|
" --tcp-keepalive Configure TCP keepalive.\n\n"
|
|
"Note: --disable-tls conflicts with --mandatory-tls or "
|
|
"--legacy-ssl\n");
|
|
|
|
exit(exit_code);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
xmpp_ctx_t *ctx;
|
|
xmpp_conn_t *conn;
|
|
xmpp_log_t *log;
|
|
char *jid = NULL, *password = NULL, *cert = NULL, *key = NULL, *host = NULL,
|
|
*capath = NULL, *cafile = NULL;
|
|
long flags = 0;
|
|
int tcp_keepalive = 0, verbosity = 0, certfail = 0;
|
|
int i;
|
|
unsigned long port = 0;
|
|
|
|
/* take a jid and password on the command line */
|
|
for (i = 1; i < argc; ++i) {
|
|
if (strcmp(argv[i], "--help") == 0)
|
|
usage(0);
|
|
else if (strcmp(argv[i], "--disable-tls") == 0)
|
|
flags |= XMPP_CONN_FLAG_DISABLE_TLS;
|
|
else if (strcmp(argv[i], "--mandatory-tls") == 0)
|
|
flags |= XMPP_CONN_FLAG_MANDATORY_TLS;
|
|
else if (strcmp(argv[i], "--trust-tls") == 0)
|
|
flags |= XMPP_CONN_FLAG_TRUST_TLS;
|
|
else if (strcmp(argv[i], "--legacy-ssl") == 0)
|
|
flags |= XMPP_CONN_FLAG_LEGACY_SSL;
|
|
else if (strcmp(argv[i], "--legacy-auth") == 0)
|
|
flags |= XMPP_CONN_FLAG_LEGACY_AUTH;
|
|
else if (strcmp(argv[i], "--verbose") == 0)
|
|
verbosity++;
|
|
else if (strcmp(argv[i], "--tcp-keepalive") == 0)
|
|
tcp_keepalive = 1;
|
|
else if (strcmp(argv[i], "--enable-certfail") == 0)
|
|
certfail = 1;
|
|
else if ((strcmp(argv[i], "--jid") == 0) && (++i < argc))
|
|
jid = argv[i];
|
|
else if ((strcmp(argv[i], "--pass") == 0) && (++i < argc))
|
|
password = argv[i];
|
|
else if ((strcmp(argv[i], "--tls-cert") == 0) && (++i < argc))
|
|
cert = argv[i];
|
|
else if ((strcmp(argv[i], "--tls-key") == 0) && (++i < argc))
|
|
key = argv[i];
|
|
else if ((strcmp(argv[i], "--capath") == 0) && (++i < argc))
|
|
capath = argv[i];
|
|
else if ((strcmp(argv[i], "--cafile") == 0) && (++i < argc))
|
|
cafile = argv[i];
|
|
else
|
|
break;
|
|
}
|
|
if ((!jid && !key) || (argc - i) > 2) {
|
|
usage(1);
|
|
}
|
|
|
|
if (i < argc)
|
|
host = argv[i];
|
|
if (i + 1 < argc)
|
|
port = strtoul(argv[i + 1], NULL, 0);
|
|
|
|
/*
|
|
* Note, this example doesn't handle errors. Applications should check
|
|
* return values of non-void functions.
|
|
*/
|
|
|
|
/* init library */
|
|
xmpp_initialize();
|
|
|
|
/* pass NULL instead to silence output */
|
|
log = xmpp_get_default_logger(XMPP_LEVEL_DEBUG);
|
|
/* create a context */
|
|
ctx = xmpp_ctx_new(NULL, log);
|
|
xmpp_ctx_set_verbosity(ctx, verbosity);
|
|
|
|
/* create a connection */
|
|
conn = xmpp_conn_new(ctx);
|
|
|
|
/* configure connection properties (optional) */
|
|
xmpp_conn_set_flags(conn, flags);
|
|
/* configure TCP keepalive (optional) */
|
|
if (tcp_keepalive)
|
|
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);
|
|
/* setup authentication information */
|
|
if (key) {
|
|
xmpp_conn_set_client_cert(conn, cert, key);
|
|
}
|
|
if (jid)
|
|
xmpp_conn_set_jid(conn, jid);
|
|
if (password)
|
|
xmpp_conn_set_pass(conn, password);
|
|
|
|
if (certfail)
|
|
xmpp_conn_set_certfail_handler(conn, certfail_handler);
|
|
if (capath)
|
|
xmpp_conn_set_capath(conn, capath);
|
|
if (cafile)
|
|
xmpp_conn_set_cafile(conn, cafile);
|
|
|
|
/* initiate connection */
|
|
if (xmpp_connect_client(conn, host, port, conn_handler, ctx) == XMPP_EOK) {
|
|
|
|
/* enter the event loop -
|
|
our connect handler will trigger an exit */
|
|
xmpp_run(ctx);
|
|
}
|
|
|
|
/* release our connection and context */
|
|
xmpp_conn_release(conn);
|
|
xmpp_ctx_free(ctx);
|
|
|
|
/* final shutdown of the library */
|
|
xmpp_shutdown();
|
|
|
|
return 0;
|
|
}
|