#include <sys/signal.h>
#include <sys/wait.h>
#include <sys/time.h>
+#include <errno.h>
#include "session.h"
#include "utils.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 guchar *session_recv_data_from_sock_unescape (Session *session,
- guint size,
- guint *actual_size);
-
-gboolean session_parent_input_cb (GIOChannel *source,
+static gboolean session_read_msg_cb (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data);
+static gboolean session_read_data_cb (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data);
+static gboolean session_write_msg_cb (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data);
+static gboolean session_write_data_cb (GIOChannel *source,
GIOCondition condition,
gpointer data);
-
-gboolean session_child_input (Session *session);
-/*!
- *\brief init session members to zero
- *
- *\param session to be initialized
- */
void session_init(Session *session)
{
- session->type = 0;
+ session->type = SESSION_UNKNOWN;
session->sock = NULL;
-
session->server = NULL;
session->port = 0;
+#if USE_OPENSSL
+ session->ssl_type = SSL_NONE;
+#endif
session->state = SESSION_READY;
session->last_access_time = time(NULL);
- session->data = NULL;
- session->read_ch = NULL;
- session->write_ch = NULL;
+ gettimeofday(&session->tv_prev, NULL);
+
+ session->conn_id = 0;
+
+ session->sock_ch = NULL;
+ session->io_tag = 0;
+
+ session->read_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->data = NULL;
}
/*!
*/
gint session_connect(Session *session, const gchar *server, gushort port)
{
- pid_t pid;
- gint pipe_fds1[2], pipe_fds2[2];
- SockInfo *sock;
- gchar *str;
-
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]);
- return -1;
- }
-
- if ((pid = fork()) < 0) {
- perror("fork");
+ 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 != 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;
- }
-
- /* child process */
+ return 0;
+}
- 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);
- debug_print("session: 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;
}
+ session->sock = sock;
+
#if USE_OPENSSL
+ sock_set_nonblocking_mode(sock, FALSE);
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);
+ g_warning("can't initialize SSL.");
+ session->state = SESSION_ERROR;
+ return -1;
}
#endif
debug_print("session: 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);
-
- while (session_child_input(session) == TRUE)
- ;
+ debug_print("session: connected\n");
- session_close(session);
-
- debug_print("session: child: disconnected\n");
+ session->state = SESSION_RECV;
+ session->sock_ch = g_io_channel_unix_new(sock->sock);
+ session->io_tag = g_io_add_watch(session->sock_ch, G_IO_IN,
+ session_read_msg_cb,
+ session);
- _exit(0);
+ return 0;
}
/*!
*/
gint session_disconnect(Session *session)
{
- debug_print("session: %s: session_disconnect()\n",
- session->child_pid == 0 ? "child" : "parent");
- session_send_msg(session, SESSION_MSG_CONTROL, "DISCONNECT");
+ session_close(session);
return 0;
}
session_close(session);
session->destroy(session);
g_free(session->server);
+ g_string_free(session->read_buf, TRUE);
+ g_byte_array_free(session->read_data_buf, TRUE);
+ g_free(session->read_data_terminator);
+ g_free(session->write_buf);
g_free(session);
}
{
g_return_val_if_fail(session != NULL, -1);
- debug_print("session: %s: session_close()\n",
- session->child_pid == 0 ? "child" : "parent");
+ debug_print("session_close\n");
- if (session->read_tag > 0) {
- g_source_remove(session->read_tag);
- session->read_tag = 0;
+ if (session->conn_id > 0) {
+ sock_connect_async_cancel(session->conn_id);
+ session->conn_id = 0;
}
-
- if (session->read_ch) {
- g_io_channel_close(session->read_ch);
- g_io_channel_unref(session->read_ch);
- session->read_ch = NULL;
+
+ if (session->io_tag > 0) {
+ g_source_remove(session->io_tag);
+ session->io_tag = 0;
}
-
- if (session->write_ch) {
- g_io_channel_close(session->write_ch);
- g_io_channel_unref(session->write_ch);
- session->write_ch = NULL;
+
+ if (session->sock_ch) {
+ g_io_channel_unref(session->sock_ch);
+ session->sock_ch = NULL;
}
if (session->sock) {
session->state = SESSION_DISCONNECTED;
}
- if (session->child_pid) {
- if (session->state != SESSION_DISCONNECTED)
- kill(session->child_pid, SIGTERM);
- waitpid(session->child_pid, NULL, 0);
- session->child_pid = 0;
- }
-
return 0;
}
-/*!
- *\brief child and parent: send control message to other process
- *
- *\param session Contains session information
- * type Kind of data (commands or message data)
- * msg Data
- *
- *\return 0 : success
- * -1 : error
- */
-gint session_send_msg(Session *session, SessionMsgType type, const gchar *msg)
+#if USE_OPENSSL
+gint session_start_tls(Session *session)
{
- gchar *prefix;
- gchar *str;
- gchar *cur;
- 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;
- cur = str = g_strdup_printf("%s %s\n", prefix, msg);
- size = strlen(str);
+ nb_mode = sock_is_nonblocking_mode(session->sock);
- while (size > 0) {
- if (g_io_channel_write(session->write_ch, cur, size,
- &bytes_written)
- != G_IO_ERROR_NONE || bytes_written == 0) {
- g_warning("%s: sending message failed.\n",
- session->child_pid == 0 ? "child" : "parent");
- g_free(str);
- return -1;
- }
- size -= bytes_written;
- cur += bytes_written;
+ if (nb_mode)
+ sock_set_nonblocking_mode(session->sock, FALSE);
+
+ if (!ssl_init_socket_with_method(session->sock, SSL_METHOD_TLSv1)) {
+ g_warning("can't start TLS session.\n");
+ if (nb_mode)
+ sock_set_nonblocking_mode(session->sock, TRUE);
+ return -1;
}
- g_free(str);
+ if (nb_mode)
+ sock_set_nonblocking_mode(session->sock, TRUE);
return 0;
}
+#endif
-/*!
- *\brief child and parent receive function
- *
- *\param session Contains session information
- *
- *\return Message read by current session
- */
-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';
+ g_return_val_if_fail(session->write_buf == NULL, -1);
+ g_return_val_if_fail(msg != NULL, -1);
+ g_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';
- break;
- }
- }
+ 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;
- return str;
+ ret = session_write_msg_cb(session->sock_ch, G_IO_OUT, session);
+
+ if (ret == TRUE)
+ session->io_tag = g_io_add_watch(session->sock_ch, G_IO_OUT,
+ session_write_msg_cb, session);
+ else if (session->state == SESSION_ERROR)
+ return -1;
+
+ return 0;
}
-#if USE_OPENSSL
-gint session_start_tls(Session *session)
+gint session_recv_msg(Session *session)
{
- gchar *ctl_msg;
+ g_return_val_if_fail(session->read_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;
+
+ session->io_tag = g_io_add_watch(session->sock_ch, G_IO_IN,
+ session_read_msg_cb, session);
return 0;
}
-#endif
/*!
*\brief parent (child?): send data to other process
*/
gint session_send_data(Session *session, const guchar *data, guint size)
{
- gchar *msg;
- const guchar *cur = data;
- 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);
+ g_return_val_if_fail(session->write_buf == NULL, -1);
+ g_return_val_if_fail(data != NULL, -1);
+ g_return_val_if_fail(size != 0, -1);
- while (size > 0) {
- if ((err = g_io_channel_write(session->write_ch, (guchar *)cur,
- 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;
- cur += bytes_written;
- debug_print("session: %s: sent %d bytes of data\n",
- session->child_pid == 0 ? "child" : "parent",
- bytes_written);
- }
+ session->state = SESSION_SEND;
+
+ session->write_buf = g_malloc(size);
+ session->write_buf_p = session->write_buf;
+ memcpy(session->write_buf, data, size);
+ session->write_buf_len = size;
+ gettimeofday(&session->tv_prev, NULL);
+
+ ret = session_write_data_cb(session->sock_ch, G_IO_OUT, session);
+
+ if (ret == TRUE)
+ session->io_tag = g_io_add_watch(session->sock_ch, 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];
+ g_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);
+ gettimeofday(&session->tv_prev, NULL);
+
+ session->io_tag = g_io_add_watch(session->sock_ch, 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;
}
-/*!
- *\brief child (parent?): read data from other process
- *
- *\param session Contains session information
- * size Bytes to read
- *
- *\return data read from session
- */
-static guchar *session_read_data(Session *session, guint size)
+static gboolean session_read_msg_cb(GIOChannel *source, GIOCondition condition,
+ 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;
+ Session *session = SESSION(data);
+ gchar buf[SESSION_BUFFSIZE];
+ gint read_len;
+ gint to_read_len;
+ gchar *newline;
+ gchar *msg;
+ gint ret;
+
+ g_return_val_if_fail(condition == G_IO_IN, FALSE);
+
+ read_len = sock_peek(session->sock, buf, sizeof(buf) - 1);
+
+ if (read_len < 0) {
+ switch (errno) {
+ case EAGAIN:
+ g_print("session_read_msg_cb: %s\n", g_strerror(errno));
+ return TRUE;
+ default:
+ g_warning("sock_peek: %s\n", g_strerror(errno));
+ session->state = SESSION_ERROR;
+ return FALSE;
}
- size -= bytes_read;
- cur += bytes_read;
- debug_print("session: %s: received %d bytes of data\n",
- session->child_pid == 0 ? "child" : "parent",
- bytes_read);
}
- return data;
-}
+ if ((newline = memchr(buf, '\n', read_len)) != NULL)
+ to_read_len = newline - buf + 1;
+ else
+ to_read_len = read_len;
+
+ read_len = sock_read(session->sock, buf, to_read_len);
+
+ /* this should always succeed */
+ if (read_len < 0) {
+ switch (errno) {
+ case EAGAIN:
+ g_print("session_read_msg_cb: %s\n", g_strerror(errno));
+ return TRUE;
+ default:
+ g_warning("sock_read: %s\n", g_strerror(errno));
+ session->state = SESSION_ERROR;
+ return FALSE;
+ }
+ }
-#define MAX_CHUNK_SIZE 4096
+ buf[read_len] = '\0';
-/*!
- *\brief child: Send session data to server
- *
- *\param session Contains session information
- * data Data to send to server
- * size Bytes to send
- *
- *\return 0 : success
- * -1 : error
- */
-static gint session_send_data_to_sock(Session *session, const guchar *data,
- guint size)
-{
- const guchar *cur = data;
- gint bytes_written;
- gint total_write_len = 0;
- guint left = size;
- gchar buf[BUFFSIZE];
- gchar *msg;
- struct timeval tv_prev, tv_cur;
-
- gettimeofday(&tv_prev, NULL);
+ /* incomplete read */
+ if (read_len == 0 || buf[read_len - 1] != '\n') {
+ g_string_append(session->read_buf, buf);
+ return TRUE;
+ }
- 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;
+ /* complete */
+ strretchomp(buf);
+ g_string_append(session->read_buf, 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), "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->io_tag > 0) {
+ g_source_remove(session->io_tag);
+ session->io_tag = 0;
}
- return 0;
+ /* callback */
+ msg = g_strdup(session->read_buf->str);
+ g_string_truncate(session->read_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;
}
-/*!
- *\brief child: Read answer/data from server
- *
- *\param session Contains session information
- * size Max bytes to receive
- *
- *\return Server answer
- */
-static guchar *session_recv_data_from_sock(Session *session, guint size)
+static gboolean session_read_data_cb(GIOChannel *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);
+ gchar buf[SESSION_BUFFSIZE];
+ GByteArray *data_buf;
+ gint read_len;
+ gint terminator_len;
+ gboolean complete = FALSE;
+ gint ret;
- gettimeofday(&tv_prev, NULL);
+ g_return_val_if_fail(condition == G_IO_IN, FALSE);
- cur = data = g_malloc(size);
+ read_len = sock_read(session->sock, buf, sizeof(buf));
- while (1) {
- bytes_read = sock_read(session->sock, cur, left);
- if (bytes_read <= 0) {
- g_free(data);
- return NULL;
+ if (read_len < 0) {
+ switch (errno) {
+ case EAGAIN:
+ g_print("session_read_data_cb: %s\n", g_strerror(errno));
+ return TRUE;
+ default:
+ g_warning("sock_read: %s\n", g_strerror(errno));
+ session->state = SESSION_ERROR;
+ return FALSE;
}
- debug_print("session: 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;
+ }
+
+ data_buf = session->read_data_buf;
+
+ g_byte_array_append(data_buf, buf, read_len);
+ terminator_len = strlen(session->read_data_terminator);
+
+ /* check if data is terminated */
+ if (read_len > 0 && 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) {
+ struct timeval tv_cur;
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;
- }
- g_free(msg);
- gettimeofday(&tv_prev, NULL);
+ 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);
+ gettimeofday(&session->tv_prev, NULL);
}
+ return TRUE;
}
- return data;
+ /* complete */
+ if (session->io_tag > 0) {
+ g_source_remove(session->io_tag);
+ session->io_tag = 0;
+ }
+
+ /* callback */
+ ret = session->recv_data_finished(session, data_buf->data,
+ data_buf->len - terminator_len);
+ session->recv_data_notify(session, data_buf->len - terminator_len,
+ session->recv_data_notify_data);
+
+ g_byte_array_set_size(data_buf, 0);
+
+ 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);
+ g_return_val_if_fail(session->write_buf != NULL, -1);
+ g_return_val_if_fail(session->write_buf_p != NULL, -1);
+ g_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:
+ g_print("session_write_cb: %s\n", g_strerror(errno));
+ 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;
+ }
- return ret_data;
-}
+ g_free(session->write_buf);
+ session->write_buf = NULL;
+ session->write_buf_p = NULL;
+ session->write_buf_len = 0;
-/*!
- *\brief Return if message is an internal command or server data
- *
- *\param str Message to analyze
- *
- *\return Type of message
- */
-static SessionMsgType session_get_msg_type(const gchar *str)
-{
- 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;
+ return 0;
}
-/*!
- *\brief parent: Received data from child
- *
- *\param source Channel watching child pipe
- * condition Unused (IN, HUP, OUT)
- * data Contains session information
- *
- *\return FALSE to remove watching channel
- */
-gboolean session_parent_input_cb(GIOChannel *source, GIOCondition condition,
- gpointer data)
+static gboolean session_write_msg_cb(GIOChannel *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;
- }
+ g_return_val_if_fail(condition == G_IO_OUT, FALSE);
+ g_return_val_if_fail(session->write_buf != NULL, FALSE);
+ g_return_val_if_fail(session->write_buf_p != NULL, FALSE);
+ g_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;
}
-/*!
- *\brief child: Receive control message from parent,
- * transfer data from/to server
- *
- *\param session Contains session information
- *
- *\return TRUE if more data is available
- */
-gboolean session_child_input(Session *session)
+static gboolean session_write_data_cb(GIOChannel *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 *session = SESSION(data);
+ guint write_buf_len;
+ gint ret;
+
+ g_return_val_if_fail(condition == G_IO_OUT, FALSE);
+ g_return_val_if_fail(session->write_buf != NULL, FALSE);
+ g_return_val_if_fail(session->write_buf_p != NULL, FALSE);
+ g_return_val_if_fail(session->write_buf_len > 0, FALSE);
+
+ write_buf_len = session->write_buf_len;
+
+ ret = session_write_buf(session);
+
+ if (ret < 0) {
session->state = SESSION_ERROR;
return FALSE;
- }
+ } else if (ret > 0) {
+ struct timeval tv_cur;
- 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->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->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->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->state = SESSION_ERROR;
- g_free(msg);
- return FALSE;
- }
- if (session_send_data(session, recv_data, size) < 0) {
- 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->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->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->state = SESSION_ERROR;
- g_free(msg);
- return FALSE;
+ gettimeofday(&tv_cur, NULL);
+ if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 ||
+ tv_cur.tv_usec - session->tv_prev.tv_usec >
+ UI_REFRESH_INTERVAL) {
+ session->send_data_progressive_notify
+ (session,
+ session->write_buf_p - session->write_buf,
+ write_buf_len,
+ session->send_data_progressive_notify_data);
+ gettimeofday(&session->tv_prev, NULL);
}
- break;
- case SESSION_MSG_ERROR:
- default:
- session_send_msg(session, SESSION_MSG_ERROR,
- "error received from parent.");
- session->state = SESSION_ERROR;
- g_free(msg);
- return FALSE;
+ return TRUE;
}
- g_free(msg);
- return TRUE;
+ if (session->io_tag > 0) {
+ g_source_remove(session->io_tag);
+ session->io_tag = 0;
+ }
+
+ /* callback */
+ ret = session->send_data_finished(session, write_buf_len);
+ session->send_data_notify(session, write_buf_len,
+ session->send_data_notify_data);
+
+ return FALSE;
}
#include <glib.h>
#include <sys/time.h>
#include <sys/types.h>
+#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#define BUFFSIZE 8192
-guint io_timeout = 60;
+typedef gint (*SockAddrFunc) (GList *addr_list,
+ gpointer data);
+
+typedef struct _SockConnectData SockConnectData;
+typedef struct _SockLookupData SockLookupData;
+typedef struct _SockAddrData SockAddrData;
+
+struct _SockConnectData {
+ gint id;
+ gchar *hostname;
+ gushort port;
+ GList *addr_list;
+ GList *cur_addr;
+ SockLookupData *lookup_data;
+ GIOChannel *channel;
+ guint io_tag;
+ SockConnectFunc func;
+ gpointer data;
+};
+
+struct _SockLookupData {
+ gchar *hostname;
+ pid_t child_pid;
+ GIOChannel *channel;
+ guint io_tag;
+ SockAddrFunc func;
+ gpointer data;
+};
+
+struct _SockAddrData {
+ gint addr_len;
+ gpointer addr_data;
+};
+
+static guint io_timeout = 60;
+
+static GList *sock_connect_data_list = NULL;
static gint sock_connect_with_timeout (gint sock,
const struct sockaddr *serv_addr,
static SockInfo *sockinfo_from_fd(const gchar *hostname,
gushort port,
gint sock);
+static void sock_address_list_free (GList *addr_list);
+
+static gboolean sock_connect_async_cb (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data);
+static gint sock_connect_async_get_address_info_cb
+ (GList *addr_list,
+ gpointer data);
+
+static gint sock_connect_address_list_async (SockConnectData *conn_data);
+
+static gboolean sock_get_address_info_async_cb (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data);
+static SockLookupData *sock_get_address_info_async
+ (const gchar *hostname,
+ gushort port,
+ SockAddrFunc func,
+ gpointer data);
+static gint sock_get_address_info_async_cancel (SockLookupData *lookup_data);
+
gint sock_set_io_timeout(guint sec)
{
struct timeval timeout;
fd_set fds;
+ if (is_nonblocking_mode(fd))
+ return 0;
+
timeout.tv_sec = io_timeout;
timeout.tv_usec = 0;
return sockinfo_from_fd(hostname, port, sock);
}
+static void sock_address_list_free(GList *addr_list)
+{
+ GList *cur;
+
+ for (cur = addr_list; cur != NULL; cur = cur->next) {
+ SockAddrData *addr_data = (SockAddrData *)cur->data;
+ g_free(addr_data->addr_data);
+ g_free(addr_data);
+ }
+
+ g_list_free(addr_list);
+}
+
+/* asynchronous TCP connection */
+
+static gboolean sock_connect_async_cb(GIOChannel *source,
+ GIOCondition condition, gpointer data)
+{
+ SockConnectData *conn_data = (SockConnectData *)data;
+ gint fd;
+ gint val;
+ gint len;
+ SockInfo *sockinfo;
+
+ fd = g_io_channel_unix_get_fd(source);
+
+ conn_data->io_tag = 0;
+ conn_data->channel = NULL;
+ g_io_channel_unref(source);
+
+ len = sizeof(val);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &val, &len) < 0) {
+ perror("getsockopt");
+ close(fd);
+ sock_connect_address_list_async(conn_data);
+ return FALSE;
+ }
+
+ if (val != 0) {
+ close(fd);
+ sock_connect_address_list_async(conn_data);
+ return FALSE;
+ }
+
+ sockinfo = g_new0(SockInfo, 1);
+ sockinfo->sock = fd;
+ sockinfo->hostname = g_strdup(conn_data->hostname);
+ sockinfo->port = conn_data->port;
+ sockinfo->state = CONN_ESTABLISHED;
+
+ conn_data->func(sockinfo, conn_data->data);
+
+ sock_connect_async_cancel(conn_data->id);
+
+ return FALSE;
+}
+
+static gint sock_connect_async_get_address_info_cb(GList *addr_list,
+ gpointer data)
+{
+ SockConnectData *conn_data = (SockConnectData *)data;
+
+ conn_data->addr_list = addr_list;
+ conn_data->cur_addr = addr_list;
+ conn_data->lookup_data = NULL;
+
+ return sock_connect_address_list_async(conn_data);
+}
+
+gint sock_connect_async(const gchar *hostname, gushort port,
+ SockConnectFunc func, gpointer data)
+{
+ static gint id = 1;
+ SockConnectData *conn_data;
+
+ conn_data = g_new0(SockConnectData, 1);
+ conn_data->id = id++;
+ conn_data->hostname = g_strdup(hostname);
+ conn_data->port = port;
+ conn_data->addr_list = NULL;
+ conn_data->cur_addr = NULL;
+ conn_data->io_tag = 0;
+ conn_data->func = func;
+ conn_data->data = data;
+
+ conn_data->lookup_data = sock_get_address_info_async
+ (hostname, port, sock_connect_async_get_address_info_cb,
+ conn_data);
+
+ if (conn_data->lookup_data == NULL) {
+ g_free(conn_data->hostname);
+ g_free(conn_data);
+ return -1;
+ }
+
+ sock_connect_data_list = g_list_append(sock_connect_data_list,
+ conn_data);
+
+ return conn_data->id;
+}
+
+gint sock_connect_async_cancel(gint id)
+{
+ SockConnectData *conn_data = NULL;
+ GList *cur;
+
+ for (cur = sock_connect_data_list; cur != NULL; cur = cur->next) {
+ if (((SockConnectData *)cur->data)->id == id) {
+ conn_data = (SockConnectData *)cur->data;
+ break;
+ }
+ }
+
+ if (conn_data) {
+ sock_connect_data_list = g_list_remove(sock_connect_data_list,
+ conn_data);
+
+ if (conn_data->lookup_data)
+ sock_get_address_info_async_cancel
+ (conn_data->lookup_data);
+
+ if (conn_data->io_tag > 0)
+ g_source_remove(conn_data->io_tag);
+ if (conn_data->channel) {
+ g_io_channel_close(conn_data->channel);
+ g_io_channel_unref(conn_data->channel);
+ }
+
+ sock_address_list_free(conn_data->addr_list);
+ g_free(conn_data->hostname);
+ g_free(conn_data);
+ } else {
+ g_warning("sock_connect_async_cancel: id %d not found.\n", id);
+ return -1;
+ }
+
+ return 0;
+}
+
+static gint sock_connect_address_list_async(SockConnectData *conn_data)
+{
+ SockAddrData *addr_data;
+ struct sockaddr_in ad;
+#ifdef INET6
+ struct sockaddr_in6 ad6;
+#endif
+ struct sockaddr *sa;
+ gint sa_size;
+ gint sock = -1;
+
+ for (; conn_data->cur_addr != NULL;
+ conn_data->cur_addr = conn_data->cur_addr->next) {
+ addr_data = (SockAddrData *)conn_data->cur_addr->data;
+
+#ifdef INET6
+ if (addr_data->addr_len != 4 && addr_data->addr_len != 16)
+ continue;
+
+ if (addr_data->addr_len == 4) {
+ /* IPv4 address */
+ if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ perror("socket");
+ conn_data->cur_addr = NULL;
+ break;
+ }
+
+ memset(&ad, 0, sizeof(ad));
+ ad.sin_family = AF_INET;
+ ad.sin_port = htons(conn_data->port);
+ memcpy(&ad.sin_addr, addr_data->addr_data,
+ addr_data->addr_len);
+
+ sa = (struct sockaddr *)&ad;
+ sa_size = sizeof(ad);
+ } else {
+ /* IPv6 address */
+ if ((sock = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
+ perror("socket");
+ conn_data->cur_addr = NULL;
+ break;
+ }
+
+ memset(&ad6, 0, sizeof(ad6));
+ ad6.sin6_family = AF_INET6;
+ ad6.sin6_port = htons(conn_data->port);
+ memcpy(&ad6.sin6_addr, addr_data->addr_data,
+ addr_data->addr_len);
+
+ sa = (struct sockaddr *)&ad6;
+ sa_size = sizeof(ad6);
+ }
+#else /* !INET6 */
+ /* IPv4 only */
+ if (addr_data->addr_len != 4)
+ continue;
+
+ if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ perror("socket");
+ conn_data->cur_addr = NULL;
+ break;
+ }
+
+ memset(&ad, 0, sizeof(ad));
+ ad.sin_family = AF_INET;
+ ad.sin_port = htons(conn_data->port);
+ memcpy(&ad.sin_addr, addr_data->addr_data, addr_data->addr_len);
+
+ sa = (struct sockaddr *)&ad;
+ sa_size = sizeof(ad);
+#endif
+
+ set_nonblocking_mode(sock, TRUE);
+
+ if (connect(sock, sa, sa_size) < 0) {
+ if (EINPROGRESS == errno) {
+ break;
+ } else {
+ perror("connect");
+ close(sock);
+ }
+ } else
+ break;
+ }
+
+ if (conn_data->cur_addr == NULL) {
+ g_warning("sock_connect_address_list_async: "
+ "connection to %s:%d failed\n",
+ conn_data->hostname, conn_data->port);
+ conn_data->func(NULL, conn_data->data);
+ sock_connect_async_cancel(conn_data->id);
+ return -1;
+ }
+
+ conn_data->cur_addr = conn_data->cur_addr->next;
+
+ conn_data->channel = g_io_channel_unix_new(sock);
+ conn_data->io_tag = g_io_add_watch(conn_data->channel, G_IO_IN|G_IO_OUT,
+ sock_connect_async_cb, conn_data);
+
+ return 0;
+}
+
+/* asynchronous DNS lookup */
+
+static gboolean sock_get_address_info_async_cb(GIOChannel *source,
+ GIOCondition condition,
+ gpointer data)
+{
+ SockLookupData *lookup_data = (SockLookupData *)data;
+ GList *addr_list = NULL;
+ SockAddrData *addr_data;
+ guint bytes_read;
+ guint8 addr_len;
+ gchar buf[16];
+
+ for (;;) {
+ if (g_io_channel_read(source, &addr_len, 1, &bytes_read)
+ != G_IO_ERROR_NONE) {
+ g_warning("sock_get_address_info_async_cb: "
+ "address length read error\n");
+ break;
+ }
+
+ if (bytes_read == 0)
+ break;
+
+ if (addr_len == 'e') {
+ g_warning("DNS lookup failed\n");
+ break;
+ } else if (addr_len != 4 && addr_len != 16) {
+ g_warning("illegal address length: %d\n", addr_len);
+ break;
+ }
+
+ if (g_io_channel_read(source, buf, addr_len, &bytes_read)
+ != G_IO_ERROR_NONE) {
+ g_warning("sock_get_address_info_async_cb: "
+ "address data read error\n");
+ break;
+ }
+
+ if (bytes_read != addr_len) {
+ g_warning("sock_get_address_info_async_cb: "
+ "incomplete address data\n");
+ break;
+ }
+
+ addr_data = g_new0(SockAddrData, 1);
+ addr_data->addr_len = addr_len;
+ addr_data->addr_data = g_malloc(addr_len);
+ memcpy(addr_data->addr_data, buf, addr_len);
+
+ addr_list = g_list_append(addr_list, addr_data);
+ }
+
+ g_io_channel_close(source);
+ g_io_channel_unref(source);
+
+ kill(lookup_data->child_pid, SIGKILL);
+ waitpid(lookup_data->child_pid, NULL, 0);
+
+ lookup_data->func(addr_list, lookup_data->data);
+
+ g_free(lookup_data->hostname);
+ g_free(lookup_data);
+
+ return FALSE;
+}
+
+static SockLookupData *sock_get_address_info_async(const gchar *hostname,
+ gushort port,
+ SockAddrFunc func,
+ gpointer data)
+{
+ SockLookupData *lookup_data = NULL;
+ gint pipe_fds[2];
+ pid_t pid;
+
+ if (pipe(pipe_fds) < 0) {
+ perror("pipe");
+ func(NULL, data);
+ return NULL;
+ }
+
+ if ((pid = fork()) < 0) {
+ perror("fork");
+ func(NULL, data);
+ return NULL;
+ }
+
+ /* child process */
+ if (pid == 0) {
+#ifdef INET6
+ gint gai_err;
+ struct addrinfo hints, *res, *ai;
+ gchar port_str[6];
+#else /* !INET6 */
+ struct hostent *hp;
+ gchar **addr_list_p;
+#endif /* INET6 */
+ guint8 addr_len;
+
+ close(pipe_fds[0]);
+
+#ifdef INET6
+ memset(&hints, 0, sizeof(hints));
+ /* hints.ai_flags = AI_CANONNAME; */
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ g_snprintf(port_str, sizeof(port_str), "%d", port);
+
+ gai_err = getaddrinfo(hostname, port_str, &hints, &res);
+ if (gai_err != 0) {
+ g_warning("getaddrinfo for %s:%s failed: %s\n",
+ hostname, port_str, gai_strerror(gai_err));
+ fd_write(pipe_fds[1], "e", 1);
+ close(pipe_fds[1]);
+ _exit(1);
+ }
+
+ for (ai = res; ai != NULL; ai = ai->ai_next) {
+ if (ai->ai_family == AF_INET) {
+ struct sockaddr_in *ad;
+
+ ad = (struct sockaddr_in *)ai->ai_addr;
+ addr_len = sizeof(ad->sin_addr);
+
+ fd_write(pipe_fds[1], &addr_len, 1);
+ fd_write_all(pipe_fds[1],
+ (gchar *)&ad->sin_addr,
+ sizeof(ad->sin_addr));
+ } else if (ai->ai_family == AF_INET6) {
+ struct sockaddr_in6 *ad;
+
+ ad = (struct sockaddr_in6 *)ai->ai_addr;
+ addr_len = sizeof(ad->sin6_addr);
+
+ fd_write(pipe_fds[1], &addr_len, 1);
+ fd_write_all(pipe_fds[1],
+ (gchar *)&ad->sin6_addr,
+ sizeof(ad->sin6_addr));
+ }
+ }
+
+ if (res != NULL)
+ freeaddrinfo(res);
+#else /* !INET6 */
+ hp = my_gethostbyname(hostname);
+ if (hp == NULL) {
+ fd_write(pipe_fds[1], "e", 1);
+ close(pipe_fds[1]);
+ _exit(1);
+ }
+
+ addr_len = (guint8)hp->h_length;
+
+ for (addr_list_p = hp->h_addr_list; *addr_list_p != NULL;
+ addr_list_p++) {
+ fd_write(pipe_fds[1], &addr_len, 1);
+ fd_write_all(pipe_fds[1], *addr_list_p, hp->h_length);
+ }
+#endif /* INET6 */
+
+ close(pipe_fds[1]);
+
+ _exit(0);
+ } else {
+ close(pipe_fds[1]);
+
+ lookup_data = g_new0(SockLookupData, 1);
+ lookup_data->hostname = g_strdup(hostname);
+ lookup_data->child_pid = pid;
+ lookup_data->func = func;
+ lookup_data->data = data;
+
+ lookup_data->channel = g_io_channel_unix_new(pipe_fds[0]);
+ lookup_data->io_tag = g_io_add_watch
+ (lookup_data->channel, G_IO_IN,
+ sock_get_address_info_async_cb, lookup_data);
+ }
+
+ return lookup_data;
+}
+
+static gint sock_get_address_info_async_cancel(SockLookupData *lookup_data)
+{
+ if (lookup_data->io_tag > 0)
+ g_source_remove(lookup_data->io_tag);
+ if (lookup_data->channel) {
+ g_io_channel_close(lookup_data->channel);
+ g_io_channel_unref(lookup_data->channel);
+ }
+
+ if (lookup_data->child_pid > 0) {
+ kill(lookup_data->child_pid, SIGKILL);
+ waitpid(lookup_data->child_pid, NULL, 0);
+ }
+
+ g_free(lookup_data->hostname);
+ g_free(lookup_data);
+
+ g_print("DNS lookup cancelled\n");
+
+ return 0;
+}
+
static SockInfo *sockinfo_from_fd(const gchar *hostname,
gushort port,
return sock_write_all(sock, "\r\n", 2);
}
-/* peek at the next socket character without actually reading it */
-gint sock_peek(SockInfo *sock)
+/* peek at the socket data without actually reading it */
+gint sock_peek(SockInfo *sock, gchar *buf, gint len)
{
- gint ret, n;
- gchar ch;
-
g_return_val_if_fail(sock != NULL, -1);
#if USE_OPENSSL
- if (sock->ssl) {
- if ((n = SSL_peek(sock->ssl, &ch, 1)) < 0)
- ret = -1;
- else
- ret = ch;
- } else {
-#endif
- if ((n = recv(sock->sock, &ch, 1, MSG_PEEK)) < 0)
- ret = -1;
- else
- ret = ch;
-#if USE_OPENSSL
- }
+ if (sock->ssl)
+ return SSL_peek(sock->ssl, buf, len);
#endif
-
- if (ret < 0)
- sock->state = CONN_DISCONNECTED;
- return ret;
+ return fd_recv(sock->sock, buf, len, MSG_PEEK);
}
gint sock_close(SockInfo *sock)
const gchar *msg);
static gint pop3_getrange_uidl_send (Pop3Session *session);
static gint pop3_getrange_uidl_recv (Pop3Session *session,
- const gchar *msg);
+ const gchar *data,
+ guint len);
static gint pop3_getsize_list_send (Pop3Session *session);
static gint pop3_getsize_list_recv (Pop3Session *session,
- const gchar *msg);
+ const gchar *data,
+ guint len);
static gint pop3_retr_send (Pop3Session *session);
static gint pop3_retr_recv (Pop3Session *session,
const gchar *data,
return PS_SUCCESS;
}
-static gint pop3_getrange_uidl_recv(Pop3Session *session, const gchar *msg)
+static gint pop3_getrange_uidl_recv(Pop3Session *session, const gchar *data,
+ guint len)
{
gchar id[IDLEN + 1];
+ gchar buf[POPBUFSIZE];
+ gint buf_len;
gint num;
time_t recv_time;
+ const gchar *p = data;
+ const gchar *lastp = data + len;
+ const gchar *newline;
- if (msg[0] == '.') {
- session->uidl_is_valid = TRUE;
- return PS_SUCCESS;
- }
+ while (p < lastp) {
+ if ((newline = memchr(p, '\r', lastp - p)) == NULL)
+ return -1;
+ buf_len = MIN(newline - p, sizeof(buf) - 1);
+ memcpy(buf, p, buf_len);
+ buf[buf_len] = '\0';
- if (sscanf(msg, "%d %" Xstr(IDLEN) "s", &num, id) != 2)
- return -1;
- if (num <= 0 || num > session->count)
- return -1;
+ p = newline + 1;
+ if (p < lastp && *p == '\n') p++;
- session->msg[num].uidl = g_strdup(id);
+ if (sscanf(buf, "%d %" Xstr(IDLEN) "s", &num, id) != 2)
+ return -1;
+ if (num <= 0 || num > session->count)
+ return -1;
- if (!session->uidl_table)
- return PS_CONTINUE;
+ session->msg[num].uidl = g_strdup(id);
- recv_time = (time_t)g_hash_table_lookup(session->uidl_table, id);
- session->msg[num].recv_time = recv_time;
+ recv_time = (time_t)g_hash_table_lookup(session->uidl_table, id);
+ session->msg[num].recv_time = recv_time;
- if (!session->ac_prefs->getall && recv_time != RECV_TIME_NONE)
- session->msg[num].received = TRUE;
+ if (!session->ac_prefs->getall && recv_time != RECV_TIME_NONE)
+ session->msg[num].received = TRUE;
- if (!session->new_msg_exist &&
- (session->ac_prefs->getall || recv_time == RECV_TIME_NONE ||
- session->ac_prefs->rmmail)) {
- session->cur_msg = num;
- session->new_msg_exist = TRUE;
+ if (!session->new_msg_exist &&
+ (session->ac_prefs->getall || recv_time == RECV_TIME_NONE ||
+ session->ac_prefs->rmmail)) {
+ session->cur_msg = num;
+ session->new_msg_exist = TRUE;
+ }
}
- return PS_CONTINUE;
+ session->uidl_is_valid = TRUE;
+ return PS_SUCCESS;
}
static gint pop3_getsize_list_send(Pop3Session *session)
return PS_SUCCESS;
}
-static gint pop3_getsize_list_recv(Pop3Session *session, const gchar *msg)
+static gint pop3_getsize_list_recv(Pop3Session *session, const gchar *data,
+ guint len)
{
+ gchar buf[POPBUFSIZE];
+ gint buf_len;
guint num, size;
+ const gchar *p = data;
+ const gchar *lastp = data + len;
+ const gchar *newline;
+
+ while (p < lastp) {
+ if ((newline = memchr(p, '\r', lastp - p)) == NULL)
+ return -1;
+ buf_len = MIN(newline - p, sizeof(buf) - 1);
+ memcpy(buf, p, buf_len);
+ buf[buf_len] = '\0';
- if (msg[0] == '.')
- return PS_SUCCESS;
+ p = newline + 1;
+ if (p < lastp && *p == '\n') p++;
- if (sscanf(msg, "%u %u", &num, &size) != 2) {
- session->error_val = PS_PROTOCOL;
- return -1;
- }
+ if (sscanf(buf, "%u %u", &num, &size) != 2) {
+ session->error_val = PS_PROTOCOL;
+ return -1;
+ }
- if (num > 0 && num <= session->count)
- session->msg[num].size = size;
- if (num > 0 && num < session->cur_msg)
- session->cur_total_bytes += size;
+ if (num > 0 && num <= session->count)
+ session->msg[num].size = size;
+ if (num > 0 && num < session->cur_msg)
+ session->cur_total_bytes += size;
+ }
- return PS_CONTINUE;
+ return PS_SUCCESS;
}
static gint pop3_retr_send(Pop3Session *session)
session = g_new0(Pop3Session, 1);
+ session_init(SESSION(session));
+
SESSION(session)->type = SESSION_POP3;
- SESSION(session)->server = NULL;
- SESSION(session)->port = 0;
- SESSION(session)->sock = NULL;
- SESSION(session)->state = SESSION_READY;
- SESSION(session)->data = NULL;
SESSION(session)->recv_msg = pop3_session_recv_msg;
SESSION(session)->recv_data_finished = pop3_session_recv_data_finished;
else
prev = cur + 1;
+ if (prev - data < len - 1 && *prev == '.' && *(prev + 1) == '.')
+ prev++;
+
if (prev - data >= len)
break;
}
pop3_getrange_last_send(pop3_session);
} else {
pop3_session->state = POP3_GETRANGE_UIDL_RECV;
- return 1;
+ session_recv_data(session, 0, ".\r\n");
}
break;
- case POP3_GETRANGE_UIDL_RECV:
- val = pop3_getrange_uidl_recv(pop3_session, body);
- if (val == PS_CONTINUE)
- return 1;
- else if (val == PS_SUCCESS) {
- if (pop3_session->new_msg_exist)
- pop3_getsize_list_send(pop3_session);
- else
- pop3_logout_send(pop3_session);
- } else
- return -1;
- break;
case POP3_GETSIZE_LIST:
pop3_session->state = POP3_GETSIZE_LIST_RECV;
- return 1;
- case POP3_GETSIZE_LIST_RECV:
- val = pop3_getsize_list_recv(pop3_session, body);
- if (val == PS_CONTINUE)
- return 1;
- else if (val == PS_SUCCESS) {
- if (pop3_lookup_next(pop3_session) == POP3_ERROR)
- return -1;
- } else
- return -1;
+ session_recv_data(session, 0, ".\r\n");
break;
case POP3_RETR:
pop3_session->state = POP3_RETR_RECV;
- session_recv_data
- (session,
- pop3_session->msg[pop3_session->cur_msg].size, TRUE);
+ session_recv_data(session, 0, ".\r\n");
break;
case POP3_DELETE:
pop3_delete_recv(pop3_session);
guint len)
{
Pop3Session *pop3_session = POP3_SESSION(session);
+ Pop3ErrorValue val = PS_SUCCESS;
- if (len == 0)
- return -1;
+ switch (pop3_session->state) {
+ case POP3_GETRANGE_UIDL_RECV:
+ val = pop3_getrange_uidl_recv(pop3_session, data, len);
+ if (val == PS_SUCCESS) {
+ if (pop3_session->new_msg_exist)
+ pop3_getsize_list_send(pop3_session);
+ else
+ pop3_logout_send(pop3_session);
+ } else
+ return -1;
+ break;
+ case POP3_GETSIZE_LIST_RECV:
+ val = pop3_getsize_list_recv(pop3_session, data, len);
+ if (val == PS_SUCCESS) {
+ if (pop3_lookup_next(pop3_session) == POP3_ERROR)
+ return -1;
+ } else
+ return -1;
+ break;
+ case POP3_RETR_RECV:
+ if (pop3_retr_recv(pop3_session, data, len) < 0)
+ return -1;
- if (pop3_retr_recv(pop3_session, data, len) < 0)
+ if (pop3_session->ac_prefs->rmmail &&
+ pop3_session->ac_prefs->msg_leave_time == 0 &&
+ pop3_session->msg[pop3_session->cur_msg].recv_time
+ != RECV_TIME_KEEP)
+ pop3_delete_send(pop3_session);
+ else if (pop3_session->cur_msg == pop3_session->count)
+ pop3_logout_send(pop3_session);
+ else {
+ pop3_session->cur_msg++;
+ if (pop3_lookup_next(pop3_session) == POP3_ERROR)
+ return -1;
+ }
+ break;
+ case POP3_ERROR:
+ default:
return -1;
-
- if (pop3_session->ac_prefs->rmmail &&
- pop3_session->ac_prefs->msg_leave_time == 0 &&
- pop3_session->msg[pop3_session->cur_msg].recv_time
- != RECV_TIME_KEEP)
- pop3_delete_send(pop3_session);
- else if (pop3_session->cur_msg == pop3_session->count)
- pop3_logout_send(pop3_session);
- else {
- pop3_session->cur_msg++;
- if (pop3_lookup_next(pop3_session) == POP3_ERROR)
- return -1;
}
return 0;