X-Git-Url: http://git.claws-mail.org/?p=claws.git;a=blobdiff_plain;f=src%2Fcommon%2Fsession.c;h=959c7a28e6785e6956dd765c893df4476fe371ad;hp=878cba14de7637cf523716f70ae1a4625a26f3c3;hb=8f81dc6211b3c5bf353e62d69d93317c59ce8d24;hpb=c83f9546d546e1cd0c083eb33c5e5e75b60e4f2c diff --git a/src/common/session.c b/src/common/session.c index 878cba14d..959c7a28e 100644 --- a/src/common/session.c +++ b/src/common/session.c @@ -1,10 +1,10 @@ /* * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client - * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * Copyright (C) 1999-2012 Hiroyuki Yamamoto and the Claws Mail team * * 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 - * the Free Software Foundation; either version 2 of the License, or + * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, @@ -13,176 +13,284 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU 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. + * along with this program. If not, see . + * */ #ifdef HAVE_CONFIG_H # include "config.h" +#include "claws-features.h" #endif #include "defs.h" #include +#include #include #include #include -#include #include -#include -#include -#include +#include +#include #include "session.h" #include "utils.h" +#include "log.h" +static gint session_connect_cb (SockInfo *sock, + gpointer data); static gint session_close (Session *session); -static gchar *session_recv_msg (Session *session); - -static guchar *session_read_data (Session *session, - guint size); - -static gint session_send_data_to_sock (Session *session, - const guchar *data, - guint size); -static guchar *session_recv_data_from_sock (Session *session, - guint size); +static gboolean session_timeout_cb (gpointer data); -static guchar *session_recv_data_from_sock_unescape (Session *session, - guint size, - guint *actual_size); +static gboolean session_recv_msg_idle_cb (gpointer data); +static gboolean session_recv_data_idle_cb (gpointer data); -gboolean session_parent_input_cb (GIOChannel *source, +static gboolean session_read_msg_cb (SockInfo *source, + GIOCondition condition, + gpointer data); +static gboolean session_read_data_cb (SockInfo *source, + GIOCondition condition, + gpointer data); +static gboolean session_write_msg_cb (SockInfo *source, + GIOCondition condition, + gpointer data); +static gboolean session_write_data_cb (SockInfo *source, GIOCondition condition, gpointer data); - -gboolean session_child_input (Session *session); -void session_init(Session *session) +void session_init(Session *session, const void *prefs_account, gboolean is_smtp) { - session->type = 0; + session->type = SESSION_UNKNOWN; session->sock = NULL; - session->server = NULL; session->port = 0; +#ifdef USE_GNUTLS + session->ssl_type = SSL_NONE; +#endif + session->nonblocking = TRUE; session->state = SESSION_READY; session->last_access_time = time(NULL); + + g_get_current_time(&session->tv_prev); + + session->conn_id = 0; + + session->io_tag = 0; + + session->read_buf_p = session->read_buf; + session->read_buf_len = 0; + + session->read_msg_buf = g_string_sized_new(1024); + session->read_data_buf = g_byte_array_new(); + + session->write_buf = NULL; + session->write_buf_p = NULL; + session->write_buf_len = 0; + + session->write_data = NULL; + session->write_data_p = NULL; + session->write_data_len = 0; + + session->timeout_tag = 0; + session->timeout_interval = 0; + session->data = NULL; + session->account = prefs_account; + session->is_smtp = is_smtp; - session->read_ch = NULL; - session->write_ch = NULL; + session->ping_tag = -1; } +/*! + *\brief Set up parent and child process + * Childloop: Read commands from parent, + * send to server, get answer, pass to parent + * + *\param session Contains session information + * server to connect to + * port to connect to + * + *\return 0 : success + * -1 : pipe / fork errors (parent) + * 1 : connection error (child) + */ gint session_connect(Session *session, const gchar *server, gushort port) { - pid_t pid; - gint pipe_fds1[2], pipe_fds2[2]; - SockInfo *sock; - gchar *str; - +#ifdef G_OS_UNIX session->server = g_strdup(server); session->port = port; - if (pipe(pipe_fds1) < 0) { - perror("pipe"); - return -1; - } - if (pipe(pipe_fds2) < 0) { - perror("pipe"); - close(pipe_fds1[0]); - close(pipe_fds1[1]); + session->conn_id = sock_connect_async(server, port, session_connect_cb, + session); + if (session->conn_id < 0) { + g_warning("can't connect to server."); + session_close(session); return -1; } - if ((pid = fork()) < 0) { - perror("fork"); - return -1; - } + return 0; +#else + SockInfo *sock; + + session->server = g_strdup(server); + session->port = port; - if (pid != 0) { - session->child_pid = pid; - session->read_ch = g_io_channel_unix_new(pipe_fds2[0]); - session->write_ch = g_io_channel_unix_new(pipe_fds1[1]); - close(pipe_fds1[0]); - close(pipe_fds2[1]); - session->read_tag = g_io_add_watch(session->read_ch, G_IO_IN, - session_parent_input_cb, - session); - return 0; + sock = sock_connect(server, port); + if (sock == NULL) { + g_warning("can't connect to server."); + session_close(session); + return -1; } + sock->is_smtp = session->is_smtp; - /* child process */ + return session_connect_cb(sock, session); +#endif +} - session->read_ch = g_io_channel_unix_new(pipe_fds1[0]); - session->write_ch = g_io_channel_unix_new(pipe_fds2[1]); - close(pipe_fds1[1]); - close(pipe_fds2[0]); +static gint session_connect_cb(SockInfo *sock, gpointer data) +{ + Session *session = SESSION(data); - g_print("child: connecting to %s:%d ...\n", server, port); + session->conn_id = 0; - if ((sock = sock_connect(server, port)) == NULL) { - session_send_msg(session, SESSION_MSG_ERROR, - "can't connect to server."); - session_close(session); - _exit(1); + if (!sock) { + g_warning("can't connect to server."); + session->state = SESSION_ERROR; + return -1; } -#if USE_OPENSSL - if (session->ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) { - session_send_msg(session, SESSION_MSG_ERROR, - "can't initialize SSL."); - session_close(session); - _exit(1); + session->sock = sock; + sock->account = session->account; + sock->is_smtp = session->is_smtp; + sock->ssl_cert_auto_accept = session->ssl_cert_auto_accept; + +#ifdef USE_GNUTLS + sock->gnutls_priority = session->gnutls_priority; + + if (session->ssl_type == SSL_TUNNEL) { + sock_set_nonblocking_mode(sock, FALSE); + if (!ssl_init_socket(sock)) { + g_warning("can't initialize SSL."); + log_error(LOG_PROTOCOL, _("SSL handshake failed\n")); + session->state = SESSION_ERROR; + return -1; + } } #endif - g_print("child: connected\n"); - - session->sock = sock; - session->state = SESSION_RECV; - - if ((str = sock_getline(sock)) == NULL) { - session_send_msg(session, SESSION_MSG_ERROR, - "can't get server response."); - session_close(session); - _exit(1); - } - strretchomp(str); - session_send_msg(session, SESSION_MSG_NORMAL, str); - g_free(str); + /* we could have gotten a timeout while waiting for user input in + * an SSL certificate dialog */ + if (session->state == SESSION_TIMEOUT) + return -1; - while (session_child_input(session) == TRUE) - ; + sock_set_nonblocking_mode(sock, session->nonblocking); - session_close(session); + debug_print("session (%p): connected\n", session); - g_print("child: disconnected\n"); + session->state = SESSION_RECV; + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_msg_cb, + session); - _exit(0); + return 0; } +/*! + *\brief child and parent: send DISCONNECT message to other process + * + *\param session Contains session information + * + *\return 0 : success + */ gint session_disconnect(Session *session) { - g_print("%s: session_disconnect()\n", session->child_pid == 0 ? "child" : "parent"); - session_send_msg(session, SESSION_MSG_CONTROL, "DISCONNECT"); + session_close(session); return 0; } +/*! + *\brief parent ? + * + *\param session Contains session information + */ void session_destroy(Session *session) { - g_return_if_fail(session != NULL); - g_return_if_fail(session->destroy != NULL); + cm_return_if_fail(session != NULL); + cm_return_if_fail(session->destroy != NULL); + + session_register_ping(session, NULL); - g_print("session_destroy()\n"); session_close(session); session->destroy(session); g_free(session->server); + g_string_free(session->read_msg_buf, TRUE); + g_byte_array_free(session->read_data_buf, TRUE); + g_free(session->read_data_terminator); + g_free(session->write_buf); +#ifdef USE_GNUTLS + g_free(session->gnutls_priority); +#endif + + debug_print("session (%p): destroyed\n", session); + g_free(session); } +gboolean session_is_running(Session *session) +{ + return (session->state == SESSION_READY || + session->state == SESSION_SEND || + session->state == SESSION_RECV); +} + +gboolean session_is_connected(Session *session) +{ + return (session->state == SESSION_SEND || + session->state == SESSION_RECV); +} + +void session_set_access_time(Session *session) +{ + session->last_access_time = time(NULL); +} + +void session_set_timeout(Session *session, guint interval) +{ + if (session->timeout_tag > 0) + g_source_remove(session->timeout_tag); + + session->timeout_interval = interval; + if (interval > 0) { + if (interval % 1000 == 0) + session->timeout_tag = + g_timeout_add_seconds(interval/1000, session_timeout_cb, session); + else + session->timeout_tag = + g_timeout_add(interval, session_timeout_cb, session); + } else + session->timeout_tag = 0; +} + +static gboolean session_timeout_cb(gpointer data) +{ + Session *session = SESSION(data); + + g_warning("session timeout.\n"); + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + session->timeout_tag = 0; + session->state = SESSION_TIMEOUT; + + return FALSE; +} + void session_set_recv_message_notify(Session *session, RecvMsgNotify notify_func, gpointer data) { @@ -222,627 +330,565 @@ void session_set_send_data_notify(Session *session, SendDataNotify notify_func, session->send_data_notify_data = data; } +/*! + *\brief child and parent cleanup (child closes first) + * + *\param session Contains session information + * + *\return 0 : success + */ static gint session_close(Session *session) { - g_return_val_if_fail(session != NULL, -1); - - g_print("%s: session_close()\n", session->child_pid == 0 ? "child" : "parent"); + cm_return_val_if_fail(session != NULL, -1); - if (session->read_tag > 0) { - g_source_remove(session->read_tag); - session->read_tag = 0; +#ifdef G_OS_UNIX + if (session->conn_id > 0) { + sock_connect_async_cancel(session->conn_id); + session->conn_id = 0; + debug_print("session (%p): connection cancelled\n", session); } +#endif - if (session->read_ch) { - g_io_channel_close(session->read_ch); - g_io_channel_unref(session->read_ch); - session->read_ch = NULL; - } - if (session->write_ch) { - g_io_channel_close(session->write_ch); - g_io_channel_unref(session->write_ch); - session->write_ch = NULL; + session_set_timeout(session, 0); + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; } if (session->sock) { sock_close(session->sock); session->sock = NULL; session->state = SESSION_DISCONNECTED; - } - - if (session->child_pid) { - kill(session->child_pid, SIGTERM); - waitpid(session->child_pid, NULL, 0); - session->child_pid = 0; + debug_print("session (%p): closed\n", session); } return 0; } -gint session_send_msg(Session *session, SessionMsgType type, const gchar *msg) +#ifdef USE_GNUTLS +gint session_start_tls(Session *session) { - gchar *prefix; - gchar *str; - guint size; - guint bytes_written; - - switch (type) { - case SESSION_MSG_NORMAL: - prefix = "MESSAGE"; break; - case SESSION_MSG_SEND_DATA: - prefix = "SENDDATA"; break; - case SESSION_MSG_RECV_DATA: - prefix = "RECVDATA"; break; - case SESSION_MSG_CONTROL: - prefix = "CONTROL"; break; - case SESSION_MSG_ERROR: - prefix = "ERROR"; break; - default: - return -1; - } + gboolean nb_mode; - str = g_strdup_printf("%s %s\n", prefix, msg); - /* g_print("%s: sending message: %s", session->child_pid == 0 ? "child" : "parent", str); */ - size = strlen(str); + nb_mode = sock_is_nonblocking_mode(session->sock); - while (size > 0) { - if (g_io_channel_write(session->write_ch, str, size, - &bytes_written) - != G_IO_ERROR_NONE || bytes_written == 0) { - g_warning("%s: sending message failed.\n", - session->child_pid == 0 ? "child" : "parent"); - return -1; - } - size -= bytes_written; + session->sock->ssl_cert_auto_accept = session->ssl_cert_auto_accept; + + if (nb_mode) + sock_set_nonblocking_mode(session->sock, FALSE); + + if (!ssl_init_socket_with_method(session->sock, SSL_METHOD_TLSv1)) { + g_warning("couldn't start TLS session.\n"); + if (nb_mode) + sock_set_nonblocking_mode(session->sock, session->nonblocking); + return -1; } + if (nb_mode) + sock_set_nonblocking_mode(session->sock, session->nonblocking); + return 0; } +#endif -static gchar *session_recv_msg(Session *session) +gint session_send_msg(Session *session, SessionMsgType type, const gchar *msg) { - gchar buf[BUFFSIZE]; - gchar *str = NULL; - guint size = 1; - guint bytes_read; - - for (;;) { - if (g_io_channel_read(session->read_ch, buf, sizeof(buf) - 1, - &bytes_read) - != G_IO_ERROR_NONE || bytes_read == 0) { - g_warning("%s: receiving message failed.\n", - session->child_pid == 0 ? "child" : "parent"); - g_free(str); - str = NULL; - break; - } + gboolean ret; - size += bytes_read; - buf[bytes_read] = '\0'; + cm_return_val_if_fail(session->write_buf == NULL, -1); + cm_return_val_if_fail(msg != NULL, -1); + cm_return_val_if_fail(msg[0] != '\0', -1); - if (!str) - str = g_strdup(buf); - else { - str = g_realloc(str, size); - strcat(str, buf); - } - if (str[size - 2] == '\n') { - str[size - 2] = '\0'; + session->state = SESSION_SEND; + session->write_buf = g_strconcat(msg, "\r\n", NULL); + session->write_buf_p = session->write_buf; + session->write_buf_len = strlen(msg) + 2; - g_print("%s: received message: %s\n", session->child_pid == 0 ? "child" : "parent", str); + ret = session_write_msg_cb(session->sock, G_IO_OUT, session); - break; - } - } + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_OUT, + session_write_msg_cb, session); + else if (session->state == SESSION_ERROR) + return -1; - return str; + return 0; } -#if USE_OPENSSL -gint session_start_tls(Session *session) +gint session_recv_msg(Session *session) { - gchar *ctl_msg; + cm_return_val_if_fail(session->read_msg_buf->len == 0, -1); - session_send_msg(session, SESSION_MSG_CONTROL, "STARTTLS"); - ctl_msg = session_recv_msg(session); - if (!ctl_msg || strcmp(ctl_msg, "CONTROL STARTTLSOK") != 0) { - g_free(ctl_msg); - return -1; - } - g_free(ctl_msg); + session->state = SESSION_RECV; + + if (session->read_buf_len > 0) + g_idle_add(session_recv_msg_idle_cb, session); + else + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_msg_cb, session); return 0; } -#endif +static gboolean session_recv_msg_idle_cb(gpointer data) +{ + Session *session = SESSION(data); + gboolean ret; + + ret = session_read_msg_cb(session->sock, G_IO_IN, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_msg_cb, session); + + return FALSE; +} + +/*! + *\brief parent (child?): send data to other process + * + *\param session Contains session information + * data Data to send + * size Bytes to send + * + *\return 0 : success + * -1 : error + */ gint session_send_data(Session *session, const guchar *data, guint size) { - gchar *msg; - guint bytes_written; - GIOError err; + gboolean ret; - session_send_msg(session, SESSION_MSG_SEND_DATA, itos(size)); - if ((msg = session_recv_msg(session)) == NULL) - return -1; - g_free(msg); + cm_return_val_if_fail(session->write_data == NULL, -1); + cm_return_val_if_fail(data != NULL, -1); + cm_return_val_if_fail(size != 0, -1); - while (size > 0) { - if ((err = g_io_channel_write(session->write_ch, (guchar *)data, - size, &bytes_written)) - != G_IO_ERROR_NONE || bytes_written == 0) { - g_warning("%s: sending data failed: %d\n", - session->child_pid == 0 ? "child" : "parent", - err); - return -1; - } - size -= bytes_written; - g_print("%s: sent %d bytes of data\n", session->child_pid == 0 ? "child" : "parent", bytes_written); - } + session->state = SESSION_SEND; + + session->write_data = data; + session->write_data_p = session->write_data; + session->write_data_len = size; + g_get_current_time(&session->tv_prev); + + ret = session_write_data_cb(session->sock, G_IO_OUT, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_OUT, + session_write_data_cb, + session); + else if (session->state == SESSION_ERROR) + return -1; return 0; } -gint session_recv_data(Session *session, guint size, gboolean unescape_dot) +gint session_recv_data(Session *session, guint size, const gchar *terminator) { - if (unescape_dot) { - gchar buf[BUFFSIZE]; + cm_return_val_if_fail(session->read_data_buf->len == 0, -1); + + session->state = SESSION_RECV; + + g_free(session->read_data_terminator); + session->read_data_terminator = g_strdup(terminator); + g_get_current_time(&session->tv_prev); + + if (session->read_buf_len > 0) + g_idle_add(session_recv_data_idle_cb, session); + else + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_data_cb, session); - g_snprintf(buf, sizeof(buf), "%d UNESCAPE", size); - session_send_msg(session, SESSION_MSG_RECV_DATA, buf); - } else - session_send_msg(session, SESSION_MSG_RECV_DATA, itos(size)); return 0; } -static guchar *session_read_data(Session *session, guint size) +static gboolean session_recv_data_idle_cb(gpointer data) { - guchar *data; - guchar *cur; - guint bytes_read; - GIOError err; - - cur = data = g_malloc(size); - - while (size > 0) { - if ((err = g_io_channel_read(session->read_ch, cur, size, - &bytes_read)) - != G_IO_ERROR_NONE || bytes_read == 0) { - g_warning("%s: reading data failed: %d\n", - session->child_pid == 0 ? "child" : "parent", - err); - g_free(data); - return NULL; - } - size -= bytes_read; - cur += bytes_read; - g_print("%s: received %d bytes of data\n", session->child_pid == 0 ? "child" : "parent", bytes_read); - } + Session *session = SESSION(data); + gboolean ret; - return data; -} + ret = session_read_data_cb(session->sock, G_IO_IN, session); -#define MAX_CHUNK_SIZE 4096 + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_data_cb, session); -static gint session_send_data_to_sock(Session *session, const guchar *data, - guint size) + return FALSE; +} + +static gboolean session_read_msg_cb(SockInfo *source, GIOCondition condition, + gpointer data) { - const guchar *cur = data; - gint bytes_written; - gint total_write_len = 0; - guint left = size; - gchar buf[BUFFSIZE]; + Session *session = SESSION(data); + gchar buf[SESSION_BUFFSIZE]; + gint line_len; + gchar *newline; gchar *msg; - struct timeval tv_prev, tv_cur; + gint ret; - gettimeofday(&tv_prev, NULL); + cm_return_val_if_fail(condition == G_IO_IN, FALSE); - while (1) { - bytes_written = sock_write(session->sock, cur, - MIN(left, MAX_CHUNK_SIZE)); - if (bytes_written <= 0) - return -1; - left -= bytes_written; - cur += bytes_written; - total_write_len += bytes_written; - if (left == 0) - break; + session_set_timeout(session, session->timeout_interval); - gettimeofday(&tv_cur, NULL); - if (tv_cur.tv_sec - tv_prev.tv_sec > 0 || - tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) { - g_snprintf(buf, sizeof(buf), "DATASENDINPROG %d %d", - total_write_len, size); - session_send_msg(session, SESSION_MSG_CONTROL, buf); - if ((msg = session_recv_msg(session)) == NULL) - return -1; - g_free(msg); - gettimeofday(&tv_prev, NULL); + if (session->read_buf_len == 0) { + gint read_len = -1; + + if (session->sock) + read_len = sock_read(session->sock, session->read_buf, + SESSION_BUFFSIZE - 1); + + if (read_len == -1 && session->state == SESSION_DISCONNECTED) { + g_warning ("sock_read: session disconnected\n"); + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + return FALSE; + } + + if (read_len == 0) { + g_warning("sock_read: received EOF\n"); + session->state = SESSION_EOF; + return FALSE; } + + if (read_len < 0) { + switch (errno) { + case EAGAIN: + return TRUE; + default: + g_warning("sock_read: %s\n", g_strerror(errno)); + session->state = SESSION_ERROR; + return FALSE; + } + } + + session->read_buf_len = read_len; } - return 0; + if ((newline = memchr(session->read_buf_p, '\n', session->read_buf_len)) + != NULL) + line_len = newline - session->read_buf_p + 1; + else + line_len = session->read_buf_len; + + if (line_len == 0) + return TRUE; + + memcpy(buf, session->read_buf_p, line_len); + buf[line_len] = '\0'; + + g_string_append(session->read_msg_buf, buf); + + session->read_buf_len -= line_len; + if (session->read_buf_len == 0) + session->read_buf_p = session->read_buf; + else + session->read_buf_p += line_len; + + /* incomplete read */ + if (buf[line_len - 1] != '\n') + return TRUE; + + /* complete */ + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + /* callback */ + msg = g_strdup(session->read_msg_buf->str); + strretchomp(msg); + g_string_truncate(session->read_msg_buf, 0); + + ret = session->recv_msg(session, msg); + session->recv_msg_notify(session, msg, session->recv_msg_notify_data); + + g_free(msg); + + if (ret < 0) + session->state = SESSION_ERROR; + + return FALSE; } -static guchar *session_recv_data_from_sock(Session *session, guint size) +static gboolean session_read_data_cb(SockInfo *source, GIOCondition condition, + gpointer data) { - guchar *data; - guchar *cur; - gint bytes_read; - gint total_read_len = 0; - guint left = size; - gchar buf[BUFFSIZE]; - gchar *msg; - struct timeval tv_prev, tv_cur; + Session *session = SESSION(data); + GByteArray *data_buf; + gint terminator_len; + gboolean complete = FALSE; + guint data_len; + gint ret; + + cm_return_val_if_fail(condition == G_IO_IN, FALSE); - gettimeofday(&tv_prev, NULL); + session_set_timeout(session, session->timeout_interval); - cur = data = g_malloc(size); + if (session->read_buf_len == 0) { + gint read_len; - while (1) { - bytes_read = sock_read(session->sock, cur, left); - if (bytes_read <= 0) { - g_free(data); - return NULL; + read_len = sock_read(session->sock, session->read_buf, + SESSION_BUFFSIZE); + + if (read_len == 0) { + g_warning("sock_read: received EOF\n"); + session->state = SESSION_EOF; + return FALSE; } - g_print("child: received %d bytes of data from sock\n", bytes_read); - left -= bytes_read; - cur += bytes_read; - total_read_len += bytes_read; - if (left == 0) - break; - gettimeofday(&tv_cur, NULL); - if (tv_cur.tv_sec - tv_prev.tv_sec > 0 || - tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) { - g_snprintf(buf, sizeof(buf), "DATARECVINPROG %d %d", - total_read_len, size); - session_send_msg(session, SESSION_MSG_CONTROL, buf); - if ((msg = session_recv_msg(session)) == NULL) { - g_free(data); - return NULL; + if (read_len < 0) { + switch (errno) { + case EAGAIN: + return TRUE; + default: + g_warning("sock_read: %s\n", g_strerror(errno)); + session->state = SESSION_ERROR; + return FALSE; } - g_free(msg); - gettimeofday(&tv_prev, NULL); } + + session->read_buf_len = read_len; + } + + data_buf = session->read_data_buf; + terminator_len = strlen(session->read_data_terminator); + + if (session->read_buf_len == 0) + return TRUE; + + g_byte_array_append(data_buf, session->read_buf_p, + session->read_buf_len); + + session->read_buf_len = 0; + session->read_buf_p = session->read_buf; + + /* check if data is terminated */ + if (data_buf->len >= terminator_len) { + if (memcmp(data_buf->data, session->read_data_terminator, + terminator_len) == 0) + complete = TRUE; + else if (data_buf->len >= terminator_len + 2 && + memcmp(data_buf->data + data_buf->len - + (terminator_len + 2), "\r\n", 2) == 0 && + memcmp(data_buf->data + data_buf->len - + terminator_len, session->read_data_terminator, + terminator_len) == 0) + complete = TRUE; + } + + /* incomplete read */ + if (!complete) { + GTimeVal tv_cur; + + g_get_current_time(&tv_cur); + if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 || + tv_cur.tv_usec - session->tv_prev.tv_usec > + UI_REFRESH_INTERVAL) { + session->recv_data_progressive_notify + (session, data_buf->len, 0, + session->recv_data_progressive_notify_data); + g_get_current_time(&session->tv_prev); + } + return TRUE; + } + + /* complete */ + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; } - return data; + data_len = data_buf->len - terminator_len; + + /* callback */ + ret = session->recv_data_finished(session, (gchar *)data_buf->data, + data_len); + + g_byte_array_set_size(data_buf, 0); + + session->recv_data_notify(session, data_len, + session->recv_data_notify_data); + + if (ret < 0) + session->state = SESSION_ERROR; + + return FALSE; } -static guchar *session_recv_data_from_sock_unescape(Session *session, - guint size, - guint *actual_size) +static gint session_write_buf(Session *session) { - GString *data; - guchar *ret_data; - gint bytes_read; - gchar buf[BUFFSIZE]; - gchar *msg; - struct timeval tv_prev, tv_cur; + gint write_len; + gint to_write_len; - gettimeofday(&tv_prev, NULL); + cm_return_val_if_fail(session->write_buf != NULL, -1); + cm_return_val_if_fail(session->write_buf_p != NULL, -1); + cm_return_val_if_fail(session->write_buf_len > 0, -1); - data = g_string_sized_new(size + 1); - *actual_size = 0; + to_write_len = session->write_buf_len - + (session->write_buf_p - session->write_buf); + to_write_len = MIN(to_write_len, SESSION_BUFFSIZE); - while (1) { - bytes_read = sock_gets(session->sock, buf, sizeof(buf)); - if (bytes_read <= 0) { - g_string_free(data, TRUE); - return NULL; - } + write_len = sock_write(session->sock, session->write_buf_p, + to_write_len); - if (buf[0] == '.' && buf[1] == '\r' && buf[2] == '\n') + if (write_len < 0) { + switch (errno) { + case EAGAIN: + write_len = 0; break; - if (buf[0] == '.' && buf[1] == '.') - g_string_append(data, buf + 1); - else - g_string_append(data, buf); - - gettimeofday(&tv_cur, NULL); - if (tv_cur.tv_sec - tv_prev.tv_sec > 0 || - tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) { - g_snprintf(buf, sizeof(buf), "DATARECVINPROG %d %d", - data->len, MAX(data->len, size)); - session_send_msg(session, SESSION_MSG_CONTROL, buf); - if ((msg = session_recv_msg(session)) == NULL) { - g_string_free(data, TRUE); - return NULL; - } - g_free(msg); - gettimeofday(&tv_prev, NULL); + default: + g_warning("sock_write: %s\n", g_strerror(errno)); + session->state = SESSION_ERROR; + return -1; } } - ret_data = data->str; - *actual_size = data->len; - g_string_free(data, FALSE); + /* incomplete write */ + if (session->write_buf_p - session->write_buf + write_len < + session->write_buf_len) { + session->write_buf_p += write_len; + return 1; + } + + g_free(session->write_buf); + session->write_buf = NULL; + session->write_buf_p = NULL; + session->write_buf_len = 0; - return ret_data; + return 0; } -static SessionMsgType session_get_msg_type(const gchar *str) +static gint session_write_data(Session *session) { - if (!strncmp(str, "MESSAGE ", 8)) - return SESSION_MSG_NORMAL; - else if (!strncmp(str, "SENDDATA ", 9)) - return SESSION_MSG_SEND_DATA; - else if (!strncmp(str, "RECVDATA ", 9)) - return SESSION_MSG_RECV_DATA; - else if (!strncmp(str, "CONTROL ", 8)) - return SESSION_MSG_CONTROL; - else if (!strncmp(str, "ERROR ", 6)) - return SESSION_MSG_ERROR; - else - return SESSION_MSG_UNKNOWN; + gint write_len; + gint to_write_len; + + cm_return_val_if_fail(session->write_data != NULL, -1); + cm_return_val_if_fail(session->write_data_p != NULL, -1); + cm_return_val_if_fail(session->write_data_len > 0, -1); + + to_write_len = session->write_data_len - + (session->write_data_p - session->write_data); + to_write_len = MIN(to_write_len, SESSION_BUFFSIZE); + + write_len = sock_write(session->sock, session->write_data_p, + to_write_len); + + if (write_len < 0) { + switch (errno) { + case EAGAIN: + write_len = 0; + break; + default: + g_warning("sock_write: %s\n", g_strerror(errno)); + session->state = SESSION_ERROR; + return -1; + } + } + + /* incomplete write */ + if (session->write_data_p - session->write_data + write_len < + session->write_data_len) { + session->write_data_p += write_len; + return 1; + } + + session->write_data = NULL; + session->write_data_p = NULL; + session->write_data_len = 0; + + return 0; } -gboolean session_parent_input_cb(GIOChannel *source, GIOCondition condition, - gpointer data) +static gboolean session_write_msg_cb(SockInfo *source, GIOCondition condition, + gpointer data) { Session *session = SESSION(data); - gchar *msg; - gchar *msg_data; - gint len; - gint total; - guchar *recv_data; - guint size; gint ret; - if ((msg = session_recv_msg(session)) == NULL) { - session->state = SESSION_ERROR; - return FALSE; - } + cm_return_val_if_fail(condition == G_IO_OUT, FALSE); + cm_return_val_if_fail(session->write_buf != NULL, FALSE); + cm_return_val_if_fail(session->write_buf_p != NULL, FALSE); + cm_return_val_if_fail(session->write_buf_len > 0, FALSE); - switch (session_get_msg_type(msg)) { - case SESSION_MSG_NORMAL: - msg_data = msg + strlen("MESSAGE "); - ret = session->recv_msg(session, msg_data); - session->recv_msg_notify(session, msg_data, - session->recv_msg_notify_data); - if (ret > 0) - session_send_msg(session, SESSION_MSG_CONTROL, - "CONTINUE"); - else if (ret < 0) { - session->state = SESSION_ERROR; - g_free(msg); - return FALSE; - } - break; - case SESSION_MSG_SEND_DATA: - msg_data = msg + strlen("SENDDATA "); - size = atoi(msg_data); - session_send_msg(session, SESSION_MSG_CONTROL, "ACCEPTDATA"); - recv_data = session_read_data(session, size); - if (!recv_data) { - session->state = SESSION_ERROR; - g_free(msg); - return FALSE; - } - ret = session->recv_data_finished(session, recv_data, size); - g_free(recv_data); - session->recv_data_notify(session, size, - session->recv_data_notify_data); - if (ret > 0) - session_send_msg(session, SESSION_MSG_CONTROL, - "CONTINUE"); - else if (ret < 0) { - session->state = SESSION_ERROR; - g_free(msg); - return FALSE; - } - break; - case SESSION_MSG_RECV_DATA: - break; - case SESSION_MSG_CONTROL: - msg_data = msg + strlen("CONTROL "); - if (!strncmp(msg_data, "DATARECVINPROG ", 15)) { - ret = sscanf(msg_data, - "DATARECVINPROG %d %d", &len, &total); - if (ret != 2) { - g_warning("wrong control message: %s\n", msg); - session->state = SESSION_ERROR; - g_free(msg); - return FALSE; - } - session_send_msg(session, SESSION_MSG_CONTROL, - "CONTINUE"); - session->recv_data_progressive_notify - (session, len, total, - session->recv_data_progressive_notify_data); - } else if (!strncmp(msg_data, "DATASENDINPROG ", 15)) { - ret = sscanf(msg_data, - "DATASENDINPROG %d %d", &len, &total); - if (ret != 2) { - g_warning("wrong control message: %s\n", msg); - session->state = SESSION_ERROR; - g_free(msg); - return FALSE; - } - session_send_msg(session, SESSION_MSG_CONTROL, - "CONTINUE"); - session->send_data_progressive_notify - (session, len, total, - session->send_data_progressive_notify_data); - } else if (!strncmp(msg_data, "DATASENT ", 9)) { - len = atoi(msg_data + 9); - ret = session->send_data_finished(session, len); - session->send_data_notify - (session, len, session->send_data_notify_data); - } else if (!strcmp(msg_data, "DISCONNECTED")) { - session->state = SESSION_DISCONNECTED; - g_free(msg); - return FALSE; - } else { - g_warning("wrong control message: %s\n", msg); - session->state = SESSION_ERROR; - g_free(msg); - return FALSE; - } - break; - case SESSION_MSG_ERROR: - default: - g_warning("error from child: %s\n", msg + strlen("ERROR ")); + ret = session_write_buf(session); + + if (ret < 0) { session->state = SESSION_ERROR; - g_free(msg); return FALSE; + } else if (ret > 0) + return TRUE; + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; } - g_free(msg); - return TRUE; + session_recv_msg(session); + + return FALSE; } -gboolean session_child_input(Session *session) +static gboolean session_write_data_cb(SockInfo *source, + GIOCondition condition, gpointer data) { - gchar buf[BUFFSIZE]; - gchar *msg; - gchar *msg_data; - gchar *str; - guchar *send_data; - guchar *recv_data; - guint size; - guint actual_size; - - if ((msg = session_recv_msg(session)) == NULL) { - session_send_msg(session, SESSION_MSG_ERROR, - "receiving message failed."); - session_close(session); + Session *session = SESSION(data); + guint write_data_len; + gint ret; + + cm_return_val_if_fail(condition == G_IO_OUT, FALSE); + cm_return_val_if_fail(session->write_data != NULL, FALSE); + cm_return_val_if_fail(session->write_data_p != NULL, FALSE); + cm_return_val_if_fail(session->write_data_len > 0, FALSE); + + write_data_len = session->write_data_len; + + ret = session_write_data(session); + + if (ret < 0) { session->state = SESSION_ERROR; return FALSE; + } else if (ret > 0) { + GTimeVal tv_cur; + + g_get_current_time(&tv_cur); + if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 || + tv_cur.tv_usec - session->tv_prev.tv_usec > + UI_REFRESH_INTERVAL) { + session_set_timeout(session, session->timeout_interval); + session->send_data_progressive_notify + (session, + session->write_data_p - session->write_data, + write_data_len, + session->send_data_progressive_notify_data); + g_get_current_time(&session->tv_prev); + } + return TRUE; } - switch (session_get_msg_type(msg)) { - case SESSION_MSG_NORMAL: - msg_data = msg + strlen("MESSAGE "); - session->state = SESSION_SEND; - sock_puts(session->sock, msg_data); - session->state = SESSION_RECV; - str = sock_getline(session->sock); - if (!str) { - session_send_msg(session, SESSION_MSG_ERROR, - "receiving message failed."); - session_close(session); - session->state = SESSION_ERROR; - g_free(msg); - return FALSE; - } - strretchomp(str); - session_send_msg(session, SESSION_MSG_NORMAL, str); - g_free(str); - break; - case SESSION_MSG_SEND_DATA: - msg_data = msg + strlen("SENDDATA "); - size = atoi(msg_data); - session_send_msg(session, SESSION_MSG_CONTROL, "ACCEPTDATA"); - send_data = session_read_data(session, size); - if (!send_data) { - session_send_msg(session, SESSION_MSG_ERROR, - "sending data failed."); - session_close(session); - session->state = SESSION_ERROR; - g_free(msg); - return FALSE; - } - session->state = SESSION_SEND; - if (session_send_data_to_sock(session, send_data, size) < 0) { - session_send_msg(session, SESSION_MSG_ERROR, - "sending data failed."); - session_close(session); - session->state = SESSION_ERROR; - g_free(send_data); - g_free(msg); - return FALSE; - } - g_free(send_data); - g_snprintf(buf, sizeof(buf), "DATASENT %d", size); - session_send_msg(session, SESSION_MSG_CONTROL, buf); - break; - case SESSION_MSG_RECV_DATA: - msg_data = msg + strlen("RECVDATA "); - size = atoi(msg_data); - session->state = SESSION_RECV; - if (strstr(msg_data, "UNESCAPE") != NULL) { - recv_data = session_recv_data_from_sock_unescape - (session, size, &actual_size); - size = actual_size; - } else - recv_data = session_recv_data_from_sock(session, size); - if (!recv_data) { - session_send_msg(session, SESSION_MSG_ERROR, - "receiving data failed."); - session_close(session); - session->state = SESSION_ERROR; - g_free(msg); - return FALSE; - } - if (session_send_data(session, recv_data, size) < 0) { - session_close(session); - session->state = SESSION_ERROR; - g_free(recv_data); - g_free(msg); - return FALSE; - } - g_free(recv_data); - break; - case SESSION_MSG_CONTROL: - msg_data = msg + strlen("CONTROL "); - if (!strcmp(msg_data, "CONTINUE")) { - session->state = SESSION_RECV; - str = sock_getline(session->sock); - if (!str) { - session_send_msg(session, SESSION_MSG_ERROR, - "receiving message failed."); - session_close(session); - session->state = SESSION_ERROR; - g_free(msg); - return FALSE; - } - strretchomp(str); - session_send_msg(session, SESSION_MSG_NORMAL, str); - g_free(str); - break; -#if USE_OPENSSL - } else if (!strcmp(msg_data, "STARTTLS")) { - if (!ssl_init_socket_with_method(session->sock, - SSL_METHOD_TLSv1)) { - session_send_msg(session, SESSION_MSG_ERROR, - "can't start TLS session."); - session_close(session); - session->state = SESSION_ERROR; - g_free(msg); - return FALSE; - } - session_send_msg(session, SESSION_MSG_CONTROL, - "STARTTLSOK"); - break; -#endif - } else if (!strcmp(msg_data, "DISCONNECT")) { - sock_close(session->sock); - session->sock = NULL; - session->state = SESSION_DISCONNECTED; - session_send_msg(session, SESSION_MSG_CONTROL, - "DISCONNECTED"); - g_free(msg); - return FALSE; - } else { - session_send_msg(session, SESSION_MSG_ERROR, - "wrong control message."); - session_close(session); - session->state = SESSION_ERROR; - g_free(msg); - return FALSE; - } - break; - case SESSION_MSG_ERROR: - default: - session_send_msg(session, SESSION_MSG_ERROR, - "error received from parent."); - session_close(session); - session->state = SESSION_ERROR; - g_free(msg); - return FALSE; + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; } - g_free(msg); - return TRUE; + /* callback */ + ret = session->send_data_finished(session, write_data_len); + session->send_data_notify(session, write_data_len, + session->send_data_notify_data); + + return FALSE; +} + +void session_register_ping(Session *session, gboolean (*ping_cb)(gpointer data)) +{ + if (!session) + return; + if (session->ping_tag > -1) + g_source_remove(session->ping_tag); + + session->ping_tag = -1; + + if (ping_cb != NULL) + session->ping_tag = g_timeout_add_seconds(60, ping_cb, session); }