diff options
Diffstat (limited to 'src/stream.c')
-rw-r--r-- | src/stream.c | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/src/stream.c b/src/stream.c new file mode 100644 index 0000000..6bb316e --- /dev/null +++ b/src/stream.c @@ -0,0 +1,642 @@ +/* iksemel (XML parser for Jabber) +** Copyright (C) 2000-2007 Gurer Ozen <madcat@e-kolay.net> +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +#include "common.h" +#include "iksemel.h" + +#ifdef HAVE_GNUTLS +#include <gnutls/gnutls.h> +#endif + +#define SF_FOREIGN 1 +#define SF_TRY_SECURE 2 +#define SF_SECURE 4 + +struct stream_data { + iksparser *prs; + ikstack *s; + ikstransport *trans; + char *name_space; + void *user_data; + const char *server; + iksStreamHook *streamHook; + iksLogHook *logHook; + iks *current; + char *buf; + void *sock; + unsigned int flags; + char *auth_username; + char *auth_pass; +#ifdef HAVE_GNUTLS + gnutls_session sess; + gnutls_certificate_credentials cred; +#endif +}; + +#ifdef HAVE_GNUTLS + +static size_t +tls_push (iksparser *prs, const char *buffer, size_t len) +{ + struct stream_data *data = iks_user_data (prs); + int ret; + + ret = data->trans->send (data->sock, buffer, len); + if (ret) return (size_t) -1; + return len; +} + +static size_t +tls_pull (iksparser *prs, char *buffer, size_t len) +{ + struct stream_data *data = iks_user_data (prs); + int ret; + + ret = data->trans->recv (data->sock, buffer, len, -1); + if (ret == -1) return (size_t) -1; + return ret; +} + +static int +handshake (struct stream_data *data) +{ + const int protocol_priority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 }; + const int kx_priority[] = { GNUTLS_KX_RSA, 0 }; + const int cipher_priority[] = { GNUTLS_CIPHER_3DES_CBC, GNUTLS_CIPHER_ARCFOUR, 0}; + const int comp_priority[] = { GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 }; + const int mac_priority[] = { GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0 }; + int ret; + + if (gnutls_global_init () != 0) + return IKS_NOMEM; + + if (gnutls_certificate_allocate_credentials (&data->cred) < 0) + return IKS_NOMEM; + + if (gnutls_init (&data->sess, GNUTLS_CLIENT) != 0) { + gnutls_certificate_free_credentials (data->cred); + return IKS_NOMEM; + } + gnutls_protocol_set_priority (data->sess, protocol_priority); + gnutls_cipher_set_priority(data->sess, cipher_priority); + gnutls_compression_set_priority(data->sess, comp_priority); + gnutls_kx_set_priority(data->sess, kx_priority); + gnutls_mac_set_priority(data->sess, mac_priority); + gnutls_credentials_set (data->sess, GNUTLS_CRD_CERTIFICATE, data->cred); + + gnutls_transport_set_push_function (data->sess, (gnutls_push_func) tls_push); + gnutls_transport_set_pull_function (data->sess, (gnutls_pull_func) tls_pull); + gnutls_transport_set_ptr (data->sess, data->prs); + + ret = gnutls_handshake (data->sess); + if (ret != 0) { + gnutls_deinit (data->sess); + gnutls_certificate_free_credentials (data->cred); + return IKS_NET_TLSFAIL; + } + + data->flags &= (~SF_TRY_SECURE); + data->flags |= SF_SECURE; + + iks_send_header (data->prs, data->server); + + return IKS_OK; +} +#endif + +static void +insert_attribs (iks *x, char **atts) +{ + if (atts) { + int i = 0; + while (atts[i]) { + iks_insert_attrib (x, atts[i], atts[i+1]); + i += 2; + } + } +} + +#define CNONCE_LEN 4 + +static void +parse_digest (char *message, const char *key, char **value_ptr, char **value_end_ptr) +{ + char *t; + + *value_ptr = NULL; + *value_end_ptr = NULL; + + t = strstr(message, key); + if (t) { + t += strlen(key); + *value_ptr = t; + while (t[0] != '\0') { + if (t[0] != '\\' && t[1] == '"') { + ++t; + *value_end_ptr = t; + return; + } + ++t; + } + } +} + +static iks * +make_sasl_response (struct stream_data *data, char *message) +{ + iks *x = NULL; + char *realm, *realm_end; + char *nonce, *nonce_end; + char cnonce[CNONCE_LEN*8 + 1]; + iksmd5 *md5; + unsigned char a1_h[16], a1[33], a2[33], response_value[33]; + char *response, *response_coded; + int i; + + parse_digest(message, "realm=\"", &realm, &realm_end); + parse_digest(message, "nonce=\"", &nonce, &nonce_end); + + /* nonce is necessary for auth */ + if (!nonce || !nonce_end) return NULL; + *nonce_end = '\0'; + + /* if no realm is given use the server hostname */ + if (realm) { + if (!realm_end) return NULL; + *realm_end = '\0'; + } else { + realm = (char *) data->server; + } + + /* generate random client challenge */ + for (i = 0; i < CNONCE_LEN; ++i) + sprintf (cnonce + i*8, "%08x", rand()); + + md5 = iks_md5_new(); + if (!md5) return NULL; + + iks_md5_hash (md5, (const unsigned char*)data->auth_username, iks_strlen (data->auth_username), 0); + iks_md5_hash (md5, (const unsigned char*)":", 1, 0); + iks_md5_hash (md5, (const unsigned char*)realm, iks_strlen (realm), 0); + iks_md5_hash (md5, (const unsigned char*)":", 1, 0); + iks_md5_hash (md5, (const unsigned char*)data->auth_pass, iks_strlen (data->auth_pass), 1); + iks_md5_digest (md5, a1_h); + + iks_md5_reset (md5); + iks_md5_hash (md5, (const unsigned char*)a1_h, 16, 0); + iks_md5_hash (md5, (const unsigned char*)":", 1, 0); + iks_md5_hash (md5, (const unsigned char*)nonce, iks_strlen (nonce), 0); + iks_md5_hash (md5, (const unsigned char*)":", 1, 0); + iks_md5_hash (md5, (const unsigned char*)cnonce, iks_strlen (cnonce), 1); + iks_md5_print (md5, (char*)a1); + + iks_md5_reset (md5); + iks_md5_hash (md5, (const unsigned char*)"AUTHENTICATE:xmpp/", 18, 0); + iks_md5_hash (md5, (const unsigned char*)data->server, iks_strlen (data->server), 1); + iks_md5_print (md5, (char*)a2); + + iks_md5_reset (md5); + iks_md5_hash (md5, (const unsigned char*)a1, 32, 0); + iks_md5_hash (md5, (const unsigned char*)":", 1, 0); + iks_md5_hash (md5, (const unsigned char*)nonce, iks_strlen (nonce), 0); + iks_md5_hash (md5, (const unsigned char*)":00000001:", 10, 0); + iks_md5_hash (md5, (const unsigned char*)cnonce, iks_strlen (cnonce), 0); + iks_md5_hash (md5, (const unsigned char*)":auth:", 6, 0); + iks_md5_hash (md5, (const unsigned char*)a2, 32, 1); + iks_md5_print (md5, (char*)response_value); + + iks_md5_delete (md5); + + i = iks_strlen (data->auth_username) + iks_strlen (realm) + + iks_strlen (nonce) + iks_strlen (data->server) + + CNONCE_LEN*8 + 136; + response = iks_malloc (i); + if (!response) return NULL; + + sprintf (response, "username=\"%s\",realm=\"%s\",nonce=\"%s\"" + ",cnonce=\"%s\",nc=00000001,qop=auth,digest-uri=\"" + "xmpp/%s\",response=%s,charset=utf-8", + data->auth_username, realm, nonce, cnonce, + data->server, response_value); + + response_coded = iks_base64_encode (response, 0); + if (response_coded) { + x = iks_new ("response"); + iks_insert_cdata (x, response_coded, 0); + iks_free (response_coded); + } + iks_free (response); + + return x; +} + +static void +iks_sasl_challenge (struct stream_data *data, iks *challenge) +{ + char *message; + iks *x; + char *tmp; + + tmp = iks_cdata (iks_child (challenge)); + if (!tmp) return; + + /* decode received blob */ + message = iks_base64_decode (tmp); + if (!message) return; + + /* reply the challenge */ + if (strstr (message, "rspauth")) { + x = iks_new ("response"); + } else { + x = make_sasl_response (data, message); + } + if (x) { + iks_insert_attrib (x, "xmlns", IKS_NS_XMPP_SASL); + iks_send (data->prs, x); + iks_delete (x); + } + iks_free (message); +} + +static int +tagHook (struct stream_data *data, char *name, char **atts, int type) +{ + iks *x; + int err; + + switch (type) { + case IKS_OPEN: + case IKS_SINGLE: +#ifdef HAVE_GNUTLS + if (data->flags & SF_TRY_SECURE) { + if (strcmp (name, "proceed") == 0) { + err = handshake (data); + return err; + } else if (strcmp (name, "failure") == 0){ + return IKS_NET_TLSFAIL; + } + } +#endif + if (data->current) { + x = iks_insert (data->current, name); + insert_attribs (x, atts); + } else { + x = iks_new (name); + insert_attribs (x, atts); + if (iks_strcmp (name, "stream:stream") == 0) { + err = data->streamHook (data->user_data, IKS_NODE_START, x); + if (err != IKS_OK) return err; + break; + } + } + data->current = x; + if (IKS_OPEN == type) break; + case IKS_CLOSE: + x = data->current; + if (NULL == x) { + err = data->streamHook (data->user_data, IKS_NODE_STOP, NULL); + if (err != IKS_OK) return err; + break; + } + if (NULL == iks_parent (x)) { + data->current = NULL; + if (iks_strcmp (name, "challenge") == 0) + iks_sasl_challenge(data, x); + else if (iks_strcmp (name, "stream:error") == 0) { + err = data->streamHook (data->user_data, IKS_NODE_ERROR, x); + if (err != IKS_OK) return err; + } else { + err = data->streamHook (data->user_data, IKS_NODE_NORMAL, x); + if (err != IKS_OK) return err; + } + break; + } + data->current = iks_parent (x); + } + return IKS_OK; +} + +static int +cdataHook (struct stream_data *data, char *cdata, size_t len) +{ + if (data->current) iks_insert_cdata (data->current, cdata, len); + return IKS_OK; +} + +static void +deleteHook (struct stream_data *data) +{ +#ifdef HAVE_GNUTLS + if (data->flags & SF_SECURE) { + gnutls_bye (data->sess, GNUTLS_SHUT_WR); + gnutls_deinit (data->sess); + gnutls_certificate_free_credentials (data->cred); + } +#endif + if (data->trans) data->trans->close (data->sock); + data->trans = NULL; + if (data->current) iks_delete (data->current); + data->current = NULL; + data->flags = 0; +} + +iksparser * +iks_stream_new (char *name_space, void *user_data, iksStreamHook *streamHook) +{ + ikstack *s; + struct stream_data *data; + + s = iks_stack_new (DEFAULT_STREAM_CHUNK_SIZE, 0); + if (NULL == s) return NULL; + data = iks_stack_alloc (s, sizeof (struct stream_data)); + memset (data, 0, sizeof (struct stream_data)); + data->s = s; + data->prs = iks_sax_extend (s, data, (iksTagHook *)tagHook, (iksCDataHook *)cdataHook, (iksDeleteHook *)deleteHook); + data->name_space = name_space; + data->user_data = user_data; + data->streamHook = streamHook; + return data->prs; +} + +void * +iks_stream_user_data (iksparser *prs) +{ + struct stream_data *data = iks_user_data (prs); + + return data->user_data; +} + +void +iks_set_log_hook (iksparser *prs, iksLogHook *logHook) +{ + struct stream_data *data = iks_user_data (prs); + + data->logHook = logHook; +} + +int +iks_connect_tcp (iksparser *prs, const char *server, int port) +{ +#ifdef USE_DEFAULT_IO + return iks_connect_with (prs, server, port, server, &iks_default_transport); +#else + return IKS_NET_NOTSUPP; +#endif +} + +int +iks_connect_via (iksparser *prs, const char *server, int port, const char *server_name) +{ +#ifdef USE_DEFAULT_IO + return iks_connect_with (prs, server, port, server_name, &iks_default_transport); +#else + return IKS_NET_NOTSUPP; +#endif +} + +int +iks_connect_with (iksparser *prs, const char *server, int port, const char *server_name, ikstransport *trans) +{ + struct stream_data *data = iks_user_data (prs); + int ret; + + if (!trans->connect) return IKS_NET_NOTSUPP; + + if (!data->buf) { + data->buf = iks_stack_alloc (data->s, NET_IO_BUF_SIZE); + if (NULL == data->buf) return IKS_NOMEM; + } + + ret = trans->connect (prs, &data->sock, server, port); + if (ret) return ret; + + data->trans = trans; + + return iks_send_header (prs, server_name); +} + +int +iks_connect_async (iksparser *prs, const char *server, int port, void *notify_data, iksAsyncNotify *notify_func) +{ +#ifdef USE_DEFAULT_IO + return iks_connect_async_with (prs, server, port, server, &iks_default_transport, notify_data, notify_func); +#else + return IKS_NET_NOTSUPP; +#endif +} + +int +iks_connect_async_with (iksparser *prs, const char *server, int port, const char *server_name, ikstransport *trans, void *notify_data, iksAsyncNotify *notify_func) +{ + struct stream_data *data = iks_user_data (prs); + int ret; + + if (NULL == trans->connect_async) + return IKS_NET_NOTSUPP; + + if (!data->buf) { + data->buf = iks_stack_alloc (data->s, NET_IO_BUF_SIZE); + if (NULL == data->buf) return IKS_NOMEM; + } + + ret = trans->connect_async (prs, &data->sock, server, server_name, port, notify_data, notify_func); + if (ret) return ret; + + data->trans = trans; + data->server = server_name; + + return IKS_OK; +} + +int +iks_connect_fd (iksparser *prs, int fd) +{ +#ifdef USE_DEFAULT_IO + struct stream_data *data = iks_user_data (prs); + + if (!data->buf) { + data->buf = iks_stack_alloc (data->s, NET_IO_BUF_SIZE); + if (NULL == data->buf) return IKS_NOMEM; + } + + data->sock = (void *) fd; + data->flags |= SF_FOREIGN; + data->trans = &iks_default_transport; + + return IKS_OK; +#else + return IKS_NET_NOTSUPP; +#endif +} + +int +iks_fd (iksparser *prs) +{ + struct stream_data *data = iks_user_data (prs); + + return (int) data->sock; +} + +int +iks_recv (iksparser *prs, int timeout) +{ + struct stream_data *data = iks_user_data (prs); + int len, ret; + + while (1) { +#ifdef HAVE_GNUTLS + if (data->flags & SF_SECURE) { + len = gnutls_record_recv (data->sess, data->buf, NET_IO_BUF_SIZE - 1); + } else +#endif + { + len = data->trans->recv (data->sock, data->buf, NET_IO_BUF_SIZE - 1, timeout); + } + if (len < 0) return IKS_NET_RWERR; + if (len == 0) break; + data->buf[len] = '\0'; + if (data->logHook) data->logHook (data->user_data, data->buf, len, 1); + ret = iks_parse (prs, data->buf, len, 0); + if (ret != IKS_OK) return ret; + if (!data->trans) { + /* stream hook called iks_disconnect */ + return IKS_NET_NOCONN; + } + timeout = 0; + } + return IKS_OK; +} + +int +iks_send_header (iksparser *prs, const char *to) +{ + struct stream_data *data = iks_user_data (prs); + char *msg; + int len, err; + + len = 91 + strlen (data->name_space) + 6 + strlen (to) + 16 + 1; + msg = iks_malloc (len); + if (!msg) return IKS_NOMEM; + sprintf (msg, "<?xml version='1.0'?>" + "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='" + "%s' to='%s' version='1.0'>", data->name_space, to); + err = iks_send_raw (prs, msg); + iks_free (msg); + if (err) return err; + data->server = to; + return IKS_OK; +} + +int +iks_send (iksparser *prs, iks *x) +{ + return iks_send_raw (prs, iks_string (iks_stack (x), x)); +} + +int +iks_send_raw (iksparser *prs, const char *xmlstr) +{ + struct stream_data *data = iks_user_data (prs); + int ret; + +#ifdef HAVE_GNUTLS + if (data->flags & SF_SECURE) { + if (gnutls_record_send (data->sess, xmlstr, strlen (xmlstr)) < 0) return IKS_NET_RWERR; + } else +#endif + { + ret = data->trans->send (data->sock, xmlstr, strlen (xmlstr)); + if (ret) return ret; + } + if (data->logHook) data->logHook (data->user_data, xmlstr, strlen (xmlstr), 0); + return IKS_OK; +} + +void +iks_disconnect (iksparser *prs) +{ + iks_parser_reset (prs); +} + +/***** tls api *****/ + +int +iks_has_tls (void) +{ +#ifdef HAVE_GNUTLS + return 1; +#else + return 0; +#endif +} + +int +iks_is_secure (iksparser *prs) +{ +#ifdef HAVE_GNUTLS + struct stream_data *data = iks_user_data (prs); + + return data->flags & SF_SECURE; +#else + return 0; +#endif +} + +int +iks_start_tls (iksparser *prs) +{ +#ifdef HAVE_GNUTLS + int ret; + struct stream_data *data = iks_user_data (prs); + + ret = iks_send_raw (prs, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"); + if (ret) return ret; + data->flags |= SF_TRY_SECURE; + return IKS_OK; +#else + return IKS_NET_NOTSUPP; +#endif +} + +/***** sasl *****/ + +int +iks_start_sasl (iksparser *prs, enum ikssasltype type, char *username, char *pass) +{ + iks *x; + + x = iks_new ("auth"); + iks_insert_attrib (x, "xmlns", IKS_NS_XMPP_SASL); + switch (type) { + case IKS_SASL_PLAIN: { + int len = iks_strlen (username) + iks_strlen (pass) + 2; + char *s = iks_malloc (80+len); + char *base64; + + iks_insert_attrib (x, "mechanism", "PLAIN"); + sprintf (s, "%c%s%c%s", 0, username, 0, pass); + base64 = iks_base64_encode (s, len); + iks_insert_cdata (x, base64, 0); + iks_free (base64); + iks_free (s); + break; + } + case IKS_SASL_DIGEST_MD5: { + struct stream_data *data = iks_user_data (prs); + + iks_insert_attrib (x, "mechanism", "DIGEST-MD5"); + data->auth_username = username; + data->auth_pass = pass; + break; + } + default: + iks_delete (x); + return IKS_NET_NOTSUPP; + } + iks_send (prs, x); + iks_delete (x); + return IKS_OK; +} |