diff -r 000000000000 -r d0f3a028347a loudmouth/src/lm-socket.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/loudmouth/src/lm-socket.c Tue Feb 02 01:10:06 2010 +0200 @@ -0,0 +1,1251 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2006 Imendio AB + * and/or its subsidiary/subsidiaries. All rights reserved. + * Copyright (C) 2007 Collabora Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include +#include + +/* Needed on Mac OS X */ +#if HAVE_NETINET_IN_H +#include +#endif + +/* Needed on Mac OS X */ +#if HAVE_ARPA_NAMESER_COMPAT_H +#include +#endif + +#include +#include + +#include "lm-debug.h" +#include "lm-internals.h" +#include "lm-misc.h" +#include "lm-ssl.h" +#include "lm-ssl-internals.h" +#include "lm-proxy.h" +#include "lm-socket.h" +#include "lm-sock.h" +#include "lm-error.h" + +#ifdef HAVE_ASYNCNS +#include +#define freeaddrinfo(x) asyncns_freeaddrinfo(x) +#endif + +#define IN_BUFFER_SIZE 1024 +#define MIN_PORT 1 +#define MAX_PORT 65536 +//#define SRV_LEN 8192 +#define SRV_LEN 512 // check the actual value needed here! - meco + +struct _LmSocket { + LmConnection *connection; + GMainContext *context; + + gchar *domain; + gchar *server; + guint port; + + gboolean blocking; + + LmSSL *ssl; + gboolean ssl_started; + LmProxy *proxy; + + GIOChannel *io_channel; + GSource *watch_in; + GSource *watch_err; + GSource *watch_hup; + + LmSocketT fd; + + GSource *watch_connect; + + gboolean cancel_open; + + GSource *watch_out; + GString *out_buf; + + LmConnectData *connect_data; + + IncomingDataFunc data_func; + SocketClosedFunc closed_func; + ConnectResultFunc connect_func; + gpointer user_data; + + guint ref_count; + +#ifdef HAVE_ASYNCNS + GSource *watch_resolv; + asyncns_query_t *resolv_query; + asyncns_t *asyncns_ctx; + GIOChannel *resolv_channel; +#endif +}; + +static void socket_free (LmSocket *socket); +static gboolean socket_do_connect (LmConnectData *connect_data); +static gboolean socket_connect_cb (GIOChannel *source, + GIOCondition condition, + LmConnectData *connect_data); +static gboolean socket_in_event (GIOChannel *source, + GIOCondition condition, + LmSocket *socket); +static gboolean socket_hup_event (GIOChannel *source, + GIOCondition condition, + LmSocket *socket); +static gboolean socket_error_event (GIOChannel *source, + GIOCondition condition, + LmSocket *socket); +static gboolean socket_buffered_write_cb (GIOChannel *source, + GIOCondition condition, + LmSocket *socket); +static gboolean socket_parse_srv_response (unsigned char *srv, + int srv_len, + gchar **out_server, + guint *out_port); +static void socket_close_io_channel (GIOChannel *io_channel); + +static void +socket_free (LmSocket *socket) +{ + g_free (socket->server); + g_free (socket->domain); + + if (socket->ssl) { + lm_ssl_unref (socket->ssl); + } + + if (socket->proxy) { + lm_proxy_unref (socket->proxy); + } + + if (socket->out_buf) { + g_string_free (socket->out_buf, TRUE); + } + + g_free (socket); +} + +gint +lm_socket_do_write (LmSocket *socket, const gchar *buf, gint len) +{ + gint b_written; + gint err; + if (socket->ssl_started) { + b_written = _lm_ssl_send (socket->ssl, buf, len); + } else { + GIOStatus io_status = G_IO_STATUS_AGAIN; + gsize bytes_written; + + while (io_status == G_IO_STATUS_AGAIN) { + io_status = g_io_channel_write_chars (socket->io_channel, + buf, len, + &bytes_written, + NULL); + } + + b_written = bytes_written; + + if (io_status != G_IO_STATUS_NORMAL) { + b_written = -1; + } + } + + err = _lm_sock_get_last_error (); + lm_verbose("[lm_socket_do_write]: socket write last error: %d\n\n",err); + return b_written; +} + +static gboolean +socket_read_incoming (LmSocket *socket, + gchar *buf, + gsize buf_size, + gsize *bytes_read, + gboolean *hangup, + gint *reason) +{ + GIOStatus status; + + *hangup = FALSE; + + if (socket->ssl_started) { + status = _lm_ssl_read (socket->ssl, + buf, buf_size - 1, bytes_read); + } else { + status = g_io_channel_read_chars (socket->io_channel, + buf, buf_size - 1, + bytes_read, + NULL); + } + +//added by prima + if (status != G_IO_STATUS_NORMAL ) { + switch (status) { + case G_IO_STATUS_EOF: + *reason = LM_DISCONNECT_REASON_HUP; + break; + case G_IO_STATUS_AGAIN: + /* No data readable but we didn't hangup */ + return FALSE; + // break; + case G_IO_STATUS_ERROR: + *reason = LM_DISCONNECT_REASON_ERROR; + break; + default: + *reason = LM_DISCONNECT_REASON_UNKNOWN; + } + + /* Notify connection_in_event that we hangup the connection */ + *hangup = TRUE; + + return FALSE; + } + + if(*bytes_read>0) + { + buf[*bytes_read] = '\0'; + return TRUE; + } + + /* There is more data to be read */ + return FALSE; + //return TRUE; +} + +static gboolean +socket_in_event (GIOChannel *source, + GIOCondition condition, + LmSocket *socket) +{ + gchar buf[IN_BUFFER_SIZE]; + gsize bytes_read = 0; + gboolean read_anything = FALSE; + gboolean hangup = 0; + gint reason = 0; + + if (!socket->io_channel) { + lm_verbose ( "[socket_in_event]:Error in io_channel\n"); + return FALSE; + } + + while (/*(condition & G_IO_IN) && */socket_read_incoming (socket, buf, IN_BUFFER_SIZE, + &bytes_read, &hangup, &reason)) { + + //g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, "\nRECV [%d]:\n", + // (int)bytes_read); + //g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, + // "-----------------------------------\n"); + //g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, "'%s'\n", buf); + //g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, + // "-----------------------------------\n"); + + lm_verbose ("\n[socket_in_event]: RECV [%d]:\n", (int)bytes_read); + lm_verbose ("-----------------------------------\n"); + lm_verbose ("'%s'\n", buf); + lm_verbose ( "-----------------------------------\n"); + lm_verbose ("Read: %d chars\n", (int)bytes_read); + + (socket->data_func) (socket, buf, socket->user_data); + + read_anything = TRUE; + + //condition = g_io_channel_get_buffer_condition (socket->io_channel); + } + + /* If we have read something, delay the hangup so that the data can be + * processed. */ + if (hangup && !read_anything) { + (socket->closed_func) (socket, reason, socket->user_data); + lm_verbose ( "[socket_in_event]:Error in hangup && !read_anything\n"); + return FALSE; + } +lm_verbose ( "[socket_in_event]:Returning TRUE\n"); + return TRUE; +} + +static gboolean +socket_hup_event (GIOChannel *source, + GIOCondition condition, + LmSocket *socket) +{ + lm_verbose ("HUP event: %d->'%s'\n", + condition, lm_misc_io_condition_to_str (condition)); + + if (!socket->io_channel) { + return FALSE; + } + + (socket->closed_func) (socket, LM_DISCONNECT_REASON_HUP, + socket->user_data); + + return TRUE; +} + +static gboolean +socket_error_event (GIOChannel *source, + GIOCondition condition, + LmSocket *socket) +{ + gint err =0; + lm_verbose ("[socket_error_event]: ERROR event: %d->'%s'\n", + condition, lm_misc_io_condition_to_str (condition)); + + err = _lm_sock_get_last_error (); + lm_verbose("[socket_error_event]: socket write last error: %d\n\n",err); + if (!socket->io_channel) { + return FALSE; + } + + (socket->closed_func) (socket, LM_DISCONNECT_REASON_ERROR, + socket->user_data); + + return TRUE; +} + +static gboolean +_lm_socket_ssl_init (LmSocket *socket, gboolean delayed) +{ + GError *error = NULL; + const gchar *ssl_verify_domain = NULL; + + lm_verbose ("[_lm_socket_ssl_init] Setting up SSL...\n"); + + _lm_ssl_initialize (socket->ssl); + +#ifdef HAVE_GNUTLS + /* GNU TLS requires the socket to be blocking */ + _lm_sock_set_blocking (socket->fd, TRUE); +#endif + + /* If we're using StartTLS, the correct thing is to verify against + * the domain. If we're using old SSL, we should verify against the + * hostname. */ + if (delayed) + ssl_verify_domain = socket->domain; + else + ssl_verify_domain = socket->server; + + if (!_lm_ssl_begin (socket->ssl, socket->fd, ssl_verify_domain, &error)) { + lm_verbose ("Could not begin SSL\n"); + + if (error) { + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, + "%s\n", error->message); + */ + lm_verbose("[_lm_socket_ssl_init]: %s\n", error->message); + g_error_free (error); + } + lm_verbose("\n[_lm_socket_ssl_init]: shutting down the socket"); + _lm_sock_shutdown (socket->fd); + _lm_sock_close (socket->fd); + + if (!delayed && socket->connect_func) { + lm_verbose("\n[_lm_socket_ssl_init]: calling the socket connect callback if ssl begin failed"); + (socket->connect_func) (socket, FALSE, socket->user_data); + } + + return FALSE; + } + +#ifdef HAVE_GNUTLS + _lm_sock_set_blocking (socket->fd, FALSE); +#endif + + socket->ssl_started = TRUE; + + return TRUE; +} + +gboolean +lm_socket_starttls (LmSocket *socket) +{ + g_return_val_if_fail (lm_ssl_get_use_starttls (socket->ssl) == TRUE, FALSE); + + return _lm_socket_ssl_init (socket, TRUE); +} + + + +void +_lm_socket_succeeded (LmConnectData *connect_data) +{ + LmSocket *socket; + + socket = connect_data->socket; + + lm_verbose ("\n[_lm_socket_succeeded]:inside.. \n"); + if (socket->watch_connect) { + g_source_destroy (socket->watch_connect); + socket->watch_connect = NULL; + } + + /* Need some way to report error/success */ + if (socket->cancel_open) { + lm_verbose ("Cancelling connection...\n"); + if (socket->connect_func) { + (socket->connect_func) (socket, FALSE, socket->user_data); + } + return; + } + + socket->fd = connect_data->fd; + socket->io_channel = connect_data->io_channel; + + freeaddrinfo (connect_data->resolved_addrs); + socket->connect_data = NULL; + g_free (connect_data); + + /* old-style ssl should be started immediately */ + if (socket->ssl && (lm_ssl_get_use_starttls (socket->ssl) == FALSE)) { + if (!_lm_socket_ssl_init (socket, FALSE)) { + return; + } + } + + socket->watch_in = + lm_misc_add_io_watch (socket->context, + socket->io_channel, + G_IO_IN, + (GIOFunc) socket_in_event, + socket); + + /* FIXME: if we add these, we don't get ANY + * response from the server, this is to do with the way that + * windows handles watches, see bug #331214. + */ +#ifndef G_OS_WIN32 + socket->watch_err = + lm_misc_add_io_watch (socket->context, + socket->io_channel, + G_IO_ERR, + (GIOFunc) socket_error_event, + socket); + + socket->watch_hup = + lm_misc_add_io_watch (socket->context, + socket->io_channel, + G_IO_HUP, + (GIOFunc) socket_hup_event, + socket); +#endif + + if (socket->connect_func) { + (socket->connect_func) (socket, TRUE, socket->user_data); + } +} + +gboolean +_lm_socket_failed_with_error (LmConnectData *connect_data, int error) +{ + LmSocket *socket; + + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, + "Connection failed: %s (error %d)\n", + _lm_sock_get_error_str (error), error);*/ + lm_verbose( "[_lm_socket_failed_with_error]: Connection failed: %s (error %d)\n",_lm_sock_get_error_str (error), error); + + socket = lm_socket_ref (connect_data->socket); + + connect_data->current_addr = connect_data->current_addr->ai_next; + + if (socket->watch_connect) { + g_source_destroy (socket->watch_connect); + socket->watch_connect = NULL; + } + + if (connect_data->io_channel != NULL) { + socket_close_io_channel (connect_data->io_channel); + } + + if (connect_data->current_addr == NULL) { + if (socket->connect_func) { + (socket->connect_func) (socket, FALSE, socket->user_data); + } + lm_verbose("\n\n[_lm_socket_failed_with_error]: connect_data->current_addr ==NULL"); + /* if the user callback called connection_close(), this is already freed */ + if (socket->connect_data != NULL) { + freeaddrinfo (connect_data->resolved_addrs); + socket->connect_data = NULL; + g_free (connect_data); + } + } else { + /* try to connect to the next host */ + lm_verbose("\n\n[_lm_socket_failed_with_error]: !!!!Trying to connect to the next host after socket connect failed with error\n"); + return socket_do_connect (connect_data); + } + + lm_socket_unref(socket); + + return FALSE; +} + +gboolean +_lm_socket_failed (LmConnectData *connect_data) +{ + return _lm_socket_failed_with_error (connect_data, + _lm_sock_get_last_error()); +} + +static gboolean +socket_connect_cb (GIOChannel *source, + GIOCondition condition, + LmConnectData *connect_data) +{ + LmSocket *socket; + struct addrinfo *addr; + int err; + socklen_t len; + LmSocketT fd; + gboolean result = FALSE; + + socket = lm_socket_ref (connect_data->socket); + addr = connect_data->current_addr; + fd = g_io_channel_unix_get_fd (source); + lm_verbose("\n[socket_connect_cb]: Inside socket_connect_cb\n"); + if (condition == G_IO_ERR) { + len = sizeof (err); + _lm_sock_get_error (fd, &err, &len); + if (!_lm_sock_is_blocking_error (err)) { + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, + "Connection failed.\n");*/ + lm_verbose( "[socket_conect_cb]: Connection failed.\n"); + + /* error condition, but might be possible to recover + * from it (by connecting to the next host) */ + if (!_lm_socket_failed_with_error (connect_data, err)) { + socket->watch_connect = NULL; + goto out; + } + } + } + + if (_lm_connection_async_connect_waiting (socket->connection)) { + gint res; + + fd = g_io_channel_unix_get_fd (source); + lm_verbose("\n[socket_conect_cb]: Async waiting calling _lm_sock_connect again\n"); + res = _lm_sock_connect (fd, addr->ai_addr, (int)addr->ai_addrlen); + if (res < 0) { + err = _lm_sock_get_last_error (); + if (_lm_sock_is_blocking_success (err)) { + _lm_connection_set_async_connect_waiting (socket->connection, FALSE); + + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, + "Connection success (1).\n");*/ + lm_verbose("[socket_connect_cb]: connection success"); + _lm_socket_succeeded (connect_data); + } + + if (_lm_connection_async_connect_waiting (socket->connection) && + !_lm_sock_is_blocking_error (err)) { + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, + "Connection failed.\n");*/ + lm_verbose("[socket_conect_cb]: connection failed"); + _lm_sock_close (connect_data->fd); + _lm_socket_failed_with_error (connect_data, err); + + socket->watch_connect = NULL; + goto out; + } + } + } else { + /* for blocking sockets, G_IO_OUT means we are connected */ + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, + "Connection success (2).\n");*/ + lm_verbose("[socket_conect_cb]: Connection success (2).\n"); + _lm_socket_succeeded (connect_data); + } + + result = TRUE; + + out: + lm_socket_unref(socket); + + return result; +} + +static gboolean +socket_do_connect (LmConnectData *connect_data) +{ + LmSocket *socket; + LmSocketT fd; + int res, err; + int port; + char name[NI_MAXHOST]; + char portname[NI_MAXSERV]; + struct addrinfo *addr; + + socket = connect_data->socket; + addr = connect_data->current_addr; + + if (socket->proxy) { + port = htons (lm_proxy_get_port (socket->proxy)); + } else { + port = htons (socket->port); + } + + ((struct sockaddr_in *) addr->ai_addr)->sin_port = port; + + res = getnameinfo (addr->ai_addr, + (socklen_t)addr->ai_addrlen, + name, sizeof (name), + portname, sizeof (portname), + NI_NUMERICHOST | NI_NUMERICSERV); + + if (res < 0) { + return _lm_socket_failed (connect_data); + } + + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, + "Trying %s port %s...\n", name, portname);*/ + lm_verbose("[socket_do_connect]: Trying %s port %s...\n", name, portname); + lm_verbose("[socket_do_connect]: calling make socket\n"); + fd = _lm_sock_makesocket (addr->ai_family, + addr->ai_socktype, + addr->ai_protocol); + lm_verbose("[socket_do_connet]: called make socket and fd"); + lm_verbose("[socket_do_connect]: The value of FD is :%d",fd); + if (!_LM_SOCK_VALID (fd)) { + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, + "Failed making socket, error:%d...\n", + _lm_sock_get_last_error ());*/ + lm_verbose( "[socket_do_connect]: Failed making socket, error:[%d]...\n", _lm_sock_get_last_error ()); + + return _lm_socket_failed (connect_data); + } + + /* Even though it says _unix_new(), it is supported by glib on + * win32 because glib does some cool stuff to find out if it + * can treat it as a FD or a windows SOCKET. + */ + connect_data->fd = fd; + connect_data->io_channel = g_io_channel_unix_new (fd); + + g_io_channel_set_encoding (connect_data->io_channel, NULL, NULL); + g_io_channel_set_buffered (connect_data->io_channel, FALSE); + + //_lm_sock_set_blocking (connect_data->fd, socket->blocking); + + if (socket->proxy) { + socket->watch_connect = + lm_misc_add_io_watch (socket->context, + connect_data->io_channel, + G_IO_OUT|G_IO_ERR, + (GIOFunc) _lm_proxy_connect_cb, + connect_data); + } else { + socket->watch_connect = + lm_misc_add_io_watch (socket->context, + connect_data->io_channel, + G_IO_OUT|G_IO_ERR, + (GIOFunc) socket_connect_cb, + connect_data); + } + + _lm_connection_set_async_connect_waiting (socket->connection, !socket->blocking); + lm_verbose("\n[socket_do_connect]: calling _lm_sock_connect\n"); + _lm_sock_set_blocking (connect_data->fd, TRUE); //prima + errno =0; + res = _lm_sock_connect (connect_data->fd, + addr->ai_addr, (int)addr->ai_addrlen); + err = _lm_sock_get_last_error (); + lm_verbose("[socket_do_connect]: socket connect last error: [%d]\n",err); + if (res < 0) { + //err = _lm_sock_get_last_error (); + lm_verbose("[socket_do_connect]: socket connect last error: %d\n",err); + if (!_lm_sock_is_blocking_error (err)) { + _lm_sock_close (connect_data->fd); + return _lm_socket_failed_with_error (connect_data, err); + } + } + _lm_sock_set_blocking (connect_data->fd, FALSE);//prima + return TRUE; +} + +gboolean +lm_socket_output_is_buffered (LmSocket *socket, + const gchar *buffer, + gint len) +{ + if (socket->out_buf) { + lm_verbose ("Appending %d bytes to output buffer\n", len); + g_string_append_len (socket->out_buf, buffer, len); + return TRUE; + } + + return FALSE; +} + +void +lm_socket_setup_output_buffer (LmSocket *socket, const gchar *buffer, gint len) +{ + lm_verbose ("OUTPUT BUFFER ENABLED\n"); + + socket->out_buf = g_string_new_len (buffer, len); + + socket->watch_out = + lm_misc_add_io_watch (socket->context, + socket->io_channel, + G_IO_OUT, + (GIOFunc) socket_buffered_write_cb, + socket); +} + +static gboolean +socket_buffered_write_cb (GIOChannel *source, + GIOCondition condition, + LmSocket *socket) +{ + gint b_written; + GString *out_buf; + /* FIXME: Do the writing */ + + out_buf = socket->out_buf; + if (!out_buf) { + /* Should not be possible */ + return FALSE; + } + + b_written = lm_socket_do_write (socket, out_buf->str, out_buf->len); + + if (b_written < 0) { + (socket->closed_func) (socket, LM_DISCONNECT_REASON_ERROR, + socket->user_data); + return FALSE; + } + + g_string_erase (out_buf, 0, (gsize) b_written); + if (out_buf->len == 0) { + lm_verbose ("Output buffer is empty, going back to normal output\n"); + + if (socket->watch_out) { + g_source_destroy (socket->watch_out); + socket->watch_out = NULL; + } + + g_string_free (out_buf, TRUE); + socket->out_buf = NULL; + return FALSE; + } + + return TRUE; +} + +static gboolean +socket_parse_srv_response (unsigned char *srv, + int srv_len, + gchar **out_server, + guint *out_port) +{ +/* int qdcount; + int ancount; + int len; + const unsigned char *pos; + unsigned char *end; + HEADER *head; + char name[256]; + char pref_name[256]; + guint pref_port = 0; + guint pref_prio = 9999; + + pref_name[0] = 0; + + pos = srv + sizeof (HEADER); + end = srv + srv_len; + head = (HEADER *) srv; + + qdcount = ntohs (head->qdcount); + ancount = ntohs (head->ancount); + + // Ignore the questions + while (qdcount-- > 0 && (len = dn_expand (srv, end, pos, name, 255)) >= 0) { + g_assert (len >= 0); + pos += len + QFIXEDSZ; + } + + // Parse the answers + while (ancount-- > 0 && (len = dn_expand (srv, end, pos, name, 255)) >= 0) { + // Ignore the initial string + uint16_t pref, weight, port; + + g_assert (len >= 0); + pos += len; + // Ignore type, ttl, class and dlen + pos += 10; + GETSHORT (pref, pos); + GETSHORT (weight, pos); + GETSHORT (port, pos); + + len = dn_expand (srv, end, pos, name, 255); + if (pref < pref_prio) { + pref_prio = pref; + strcpy (pref_name, name); + pref_port = port; + } + pos += len; + } + + if (pref_name[0]) { + *out_server = g_strdup (pref_name); + *out_port = pref_port; + return TRUE; + } */ + return FALSE; +} + +static void +socket_close_io_channel (GIOChannel *io_channel) +{ + gint fd; + + g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, + "Freeing up IOChannel and file descriptor\n"); + + fd = g_io_channel_unix_get_fd (io_channel); + + g_io_channel_unref (io_channel); + + _lm_sock_close (fd); +} + +static void +_lm_socket_create_phase1 (LmSocket *socket, unsigned char *srv_ans, int len); +static void +_lm_socket_create_phase2 (LmSocket *socket, struct addrinfo *ans); + +#ifdef HAVE_ASYNCNS +#define PHASE_1 0 +#define PHASE_2 1 + +static gboolean +_lm_socket_resolver_done (GSource *source, + GIOCondition condition, + gpointer data); + + +static void +_asyncns_done (LmSocket *socket) +{ + if (socket->resolv_channel != NULL) { + g_io_channel_unref (socket->resolv_channel); + socket->resolv_channel = NULL; + } + + if (socket->watch_resolv) { + g_source_destroy(socket->watch_resolv); + socket->watch_resolv = NULL; + } + + if (socket->asyncns_ctx) { + asyncns_free (socket->asyncns_ctx); + socket->asyncns_ctx = NULL; + } + + socket->resolv_query = NULL; +} + +void _asyncns_cancel (LmSocket *socket) +{ + if (socket == NULL) + return; + + if (socket->asyncns_ctx) { + if (socket->resolv_query) + asyncns_cancel (socket->asyncns_ctx, socket->resolv_query); + + _asyncns_done (socket); + } +} + +static gboolean +_asyncns_prep (LmSocket *socket, GError **error) +{ + if (socket->asyncns_ctx) { + return TRUE; + } + + socket->asyncns_ctx = asyncns_new (1); + if (socket->asyncns_ctx == NULL) { + g_set_error (error, + LM_ERROR, + LM_ERROR_CONNECTION_FAILED, + "can't initialise libasyncns"); + return FALSE; + } + + socket->resolv_channel = + g_io_channel_unix_new (asyncns_fd (socket->asyncns_ctx)); + + socket->watch_resolv = + lm_misc_add_io_watch (socket->context, + socket->resolv_channel, + G_IO_IN, + (GIOFunc) _lm_socket_resolver_done, + socket); + + return TRUE; +} + +static gboolean +_lm_socket_resolver_done (GSource *source, + GIOCondition condition, + gpointer data) +{ + LmSocket *socket = lm_socket_ref ((LmSocket *) data); + struct addrinfo *ans; + unsigned char *srv_ans; + int err; + gboolean result = FALSE; + + /* process pending data */ + asyncns_wait (socket->asyncns_ctx, FALSE); + + if (!asyncns_isdone (socket->asyncns_ctx, socket->resolv_query)) { + result = TRUE; + } else { + switch ((guint) asyncns_getuserdata (socket->asyncns_ctx, socket->resolv_query)) { + case PHASE_1: + err = asyncns_res_done (socket->asyncns_ctx, socket->resolv_query, &srv_ans); + socket->resolv_query = NULL; + _lm_socket_create_phase1 (socket, (err <= 0) ? NULL : srv_ans, err); + result = TRUE; + break; + case PHASE_2: + err = asyncns_getaddrinfo_done (socket->asyncns_ctx, socket->resolv_query, &ans); + socket->resolv_query = NULL; + _lm_socket_create_phase2 (socket, (err) ? NULL : ans); + _asyncns_done (socket); + break; + default: + g_assert_not_reached(); + break; + } + } + + lm_socket_unref(socket); + + return result; +} + +#endif + +static void +_lm_socket_create_phase1 (LmSocket *socket, + unsigned char *srv_ans, + int len) +{ + const char *remote_addr; + LmConnectData *data; + struct addrinfo req; +#ifndef HAVE_ASYNCNS + struct addrinfo *ans; + int err; +#endif + + if (srv_ans != NULL) { + gchar *new_server; + guint new_port; + gboolean result; + result = socket_parse_srv_response (srv_ans, len, + &new_server, + &new_port); + if (result == TRUE) { + g_free (socket->server); + socket->server = new_server; + socket->port = new_port; + } + } + + /* If server wasn't specified and SRV failed, use domain */ + if (!socket->server) { + lm_verbose ("SRV lookup failed, trying jid domain\n"); + socket->server = g_strdup (socket->domain); + } + + if (socket->proxy) { + remote_addr = lm_proxy_get_server (socket->proxy); + } else { + remote_addr = socket->server; + } + + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_NET, + "Going to connect to %s%s:%u\n", (socket->proxy) ? "proxy " : "", + remote_addr, socket->port);*/ + lm_verbose( "[_lm_socket_create_phase1]: Going to connect to %s%s:%u\n", (socket->proxy) ? "proxy " : "", + remote_addr, socket->port); + + data = g_new0 (LmConnectData, 1); + + data->socket = socket; + data->connection = socket->connection; + data->fd = -1; + socket->connect_data = data; + + memset (&req, 0, sizeof(req)); + req.ai_family = AF_UNSPEC; + req.ai_socktype = SOCK_STREAM; + req.ai_protocol = IPPROTO_TCP; + +#ifdef HAVE_ASYNCNS + if (!_asyncns_prep (socket, NULL)) + return; + + socket->resolv_query = + asyncns_getaddrinfo (socket->asyncns_ctx, + remote_addr, + NULL, + &req); + + asyncns_setuserdata (socket->asyncns_ctx, + socket->resolv_query, + (gpointer) PHASE_2); +#else + err = getaddrinfo (remote_addr, NULL, &req, &ans); + lm_verbose( "[_lm_socket_create_phase1]: after getaddrinfo : %u\n", err); + _lm_socket_create_phase2 (socket, (err) ? NULL : ans); + if (err != 0) { + return; + } +#endif +} + +static void +_lm_socket_create_phase2 (LmSocket *socket, struct addrinfo *ans) +{ + if (ans == NULL) { + lm_verbose ("error while resolving, bailing out\n"); + if (socket->connect_func) { + (socket->connect_func) (socket, FALSE, socket->user_data); + } + g_free (socket->connect_data); + socket->connect_data = NULL; + return; + } + + socket->connect_data->resolved_addrs = ans; + socket->connect_data->current_addr = ans; + + socket_do_connect (socket->connect_data); +} + +LmSocket * +lm_socket_create (GMainContext *context, + IncomingDataFunc data_func, + SocketClosedFunc closed_func, + ConnectResultFunc connect_func, + gpointer user_data, + LmConnection *connection, + gboolean blocking, + const gchar *server, + const gchar *domain, + guint port, + LmSSL *ssl, + LmProxy *proxy, + GError **error) +{ + LmSocket *socket; + +#ifndef HAVE_ASYNCNS + unsigned char srv_ans[SRV_LEN]; + int len; +#endif + lm_verbose ("[lm_socket_create]: inside.."); + + g_return_val_if_fail (domain != NULL, NULL); + g_return_val_if_fail ((port >= MIN_PORT && port <= MAX_PORT), NULL); + g_return_val_if_fail (data_func != NULL, NULL); + g_return_val_if_fail (closed_func != NULL, NULL); + g_return_val_if_fail (connect_func != NULL, NULL); + + + socket = g_new0 (LmSocket, 1); + lm_verbose ("[lm_socket_create]: after g_new0 "); + + socket->ref_count = 1; + + socket->connection = connection; + socket->domain = g_strdup (domain); + socket->server = g_strdup (server); + socket->port = port; + socket->cancel_open = FALSE; + socket->ssl = ssl; + socket->ssl_started = FALSE; + socket->proxy = NULL; + socket->blocking = blocking; + + if (context) { + socket->context = g_main_context_ref (context); + } + + if (proxy) { + socket->proxy = lm_proxy_ref (proxy); + } + + if (!server) { + /*char *srv; + srv = g_strdup_printf ("_xmpp-client._tcp.%s", socket->domain); + lm_verbose ("Performing a SRV lookup for %s\n", srv); + +#ifdef HAVE_ASYNCNS + if (!_asyncns_prep (socket, error)) + return NULL; + + socket->resolv_query = + asyncns_res_query (socket->asyncns_ctx, srv, C_IN, T_SRV); + asyncns_setuserdata (socket->asyncns_ctx, socket->resolv_query, (gpointer) PHASE_1); +#else + res_init (); + + len = res_query (srv, C_IN, T_SRV, srv_ans, SRV_LEN); + _lm_socket_create_phase1 (socket, (len < 1) ? NULL : srv_ans, len); + g_free (srv); +#endif*/ + } else { + lm_verbose ("SRV lookup disabled for %s\n", socket->server); + _lm_socket_create_phase1 (socket, NULL, 0); + } + + if (socket->connect_data == NULL) { + /* Open failed synchronously, probably a DNS lookup problem */ + lm_socket_unref(socket); + + g_set_error (error, + LM_ERROR, + LM_ERROR_CONNECTION_FAILED, + "Failed to resolve server"); + + return NULL; + } + + + /* If the connection fails synchronously, we don't want to call the + * connect_func to indicate an error, we return an error indication + * instead. So, we delay saving the functions until after we know + * we are going to return success. + */ + socket->data_func = data_func; + socket->closed_func = closed_func; + socket->connect_func = connect_func; + socket->user_data = user_data; + + return socket; +} + +void +lm_socket_flush (LmSocket *socket) +{ + g_return_if_fail (socket != NULL); + g_return_if_fail (socket->io_channel != NULL); + + g_io_channel_flush (socket->io_channel, NULL); +} + +void +lm_socket_close (LmSocket *socket) +{ + LmConnectData *data; + int ret = 0; + g_return_if_fail (socket != NULL); + + if (socket->watch_connect) { + g_source_destroy (socket->watch_connect); + socket->watch_connect = NULL; + } + + _lm_sock_close(socket->fd); + data = socket->connect_data; + ret = errno; lm_verbose("\n\n[lm_socket_close]: The errno after close fd is: %d",ret); + if (data) { + freeaddrinfo (data->resolved_addrs); + socket->connect_data = NULL; + g_free (data); + } + + if (socket->io_channel) { + if (socket->watch_in) { + g_source_destroy (socket->watch_in); + socket->watch_in = NULL; + } + + if (socket->watch_err) { + g_source_destroy (socket->watch_err); + socket->watch_err = NULL; + } + + if (socket->watch_hup) { + g_source_destroy (socket->watch_hup); + socket->watch_hup = NULL; + } + + if (socket->watch_out) { + g_source_destroy (socket->watch_out); + socket->watch_out = NULL; + } + + socket_close_io_channel (socket->io_channel); + + socket->io_channel = NULL; + socket->fd = -1; + } +} + +gchar * +lm_socket_get_local_host (LmSocket *socket) +{ + return _lm_sock_get_local_host (socket->fd); +} + +LmSocket * +lm_socket_ref (LmSocket *socket) +{ + g_return_val_if_fail (socket != NULL, NULL); + + socket->ref_count++; + + return socket; +} + +void +lm_socket_unref (LmSocket *socket) +{ + g_return_if_fail (socket != NULL); + + socket->ref_count--; + + if (socket->ref_count <= 0) { + socket_free (socket); + } +} + +gboolean +lm_socket_set_keepalive (LmSocket *socket, int delay) +{ +#ifdef USE_TCP_KEEPALIVES + return _lm_sock_set_keepalive (socket->fd, delay); +#else + return FALSE; +#endif /* USE_TCP_KEEPALIVES */ +} +