2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2003 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
31 #include <sys/types.h>
33 #include <sys/signal.h>
39 static gint session_close (Session *session);
41 static gchar *session_recv_msg (Session *session);
43 static guchar *session_read_data (Session *session,
46 static gint session_send_data_to_sock (Session *session,
49 static guchar *session_recv_data_from_sock (Session *session,
52 gboolean session_parent_input_cb (GIOChannel *source,
53 GIOCondition condition,
56 gboolean session_child_input (Session *session);
59 void session_init(Session *session)
64 session->server = NULL;
66 session->state = SESSION_READY;
67 session->last_access_time = time(NULL);
70 session->read_ch = NULL;
71 session->write_ch = NULL;
74 gint session_connect(Session *session, const gchar *server, gushort port)
77 gint pipe_fds1[2], pipe_fds2[2];
81 session->server = g_strdup(server);
84 if (pipe(pipe_fds1) < 0) {
88 if (pipe(pipe_fds2) < 0) {
95 if ((pid = fork()) < 0) {
101 session->child_pid = pid;
102 session->read_ch = g_io_channel_unix_new(pipe_fds2[0]);
103 session->write_ch = g_io_channel_unix_new(pipe_fds1[1]);
106 session->read_tag = g_io_add_watch(session->read_ch, G_IO_IN,
107 session_parent_input_cb,
114 session->read_ch = g_io_channel_unix_new(pipe_fds1[0]);
115 session->write_ch = g_io_channel_unix_new(pipe_fds2[1]);
119 g_print("child: connecting to %s:%d ...\n", server, port);
121 if ((sock = sock_connect(server, port)) == NULL) {
122 session_send_msg(session, SESSION_MSG_ERROR,
123 "can't connect to server.");
124 session_close(session);
129 if (session->ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
130 session_send_msg(session, SESSION_MSG_ERROR,
131 "can't initialize SSL.");
132 session_close(session);
137 g_print("child: connected\n");
139 session->sock = sock;
140 session->state = SESSION_RECV;
142 if ((str = sock_getline(sock)) == NULL) {
143 session_send_msg(session, SESSION_MSG_ERROR,
144 "can't get server response.");
145 session_close(session);
149 session_send_msg(session, SESSION_MSG_NORMAL, str);
152 while (session_child_input(session) == TRUE)
155 session_close(session);
157 g_print("child: disconnected\n");
162 gint session_disconnect(Session *session)
164 g_print("%s: session_disconnect()\n", session->child_pid == 0 ? "child" : "parent");
165 session_send_msg(session, SESSION_MSG_CONTROL, "DISCONNECT");
169 void session_destroy(Session *session)
171 g_return_if_fail(session != NULL);
172 g_return_if_fail(session->destroy != NULL);
174 g_print("session_destroy()\n");
175 session_close(session);
176 session->destroy(session);
177 g_free(session->server);
181 void session_set_recv_message_notify(Session *session,
182 RecvMsgNotify notify_func, gpointer data)
184 session->recv_msg_notify = notify_func;
185 session->recv_msg_notify_data = data;
188 void session_set_recv_data_progressive_notify
190 RecvDataProgressiveNotify notify_func,
193 session->recv_data_progressive_notify = notify_func,
194 session->recv_data_progressive_notify_data = data;
197 void session_set_recv_data_notify(Session *session, RecvDataNotify notify_func,
200 session->recv_data_notify = notify_func;
201 session->recv_data_notify_data = data;
204 void session_set_send_data_progressive_notify
206 SendDataProgressiveNotify notify_func,
209 session->send_data_progressive_notify = notify_func;
210 session->send_data_progressive_notify_data = data;
213 void session_set_send_data_notify(Session *session, SendDataNotify notify_func,
216 session->send_data_notify = notify_func;
217 session->send_data_notify_data = data;
220 static gint session_close(Session *session)
222 g_return_val_if_fail(session != NULL, -1);
224 g_print("%s: session_close()\n", session->child_pid == 0 ? "child" : "parent");
226 if (session->read_tag > 0) {
227 g_source_remove(session->read_tag);
228 session->read_tag = 0;
231 if (session->read_ch) {
232 g_io_channel_close(session->read_ch);
233 g_io_channel_unref(session->read_ch);
234 session->read_ch = NULL;
236 if (session->write_ch) {
237 g_io_channel_close(session->write_ch);
238 g_io_channel_unref(session->write_ch);
239 session->write_ch = NULL;
243 sock_close(session->sock);
244 session->sock = NULL;
245 session->state = SESSION_DISCONNECTED;
248 if (session->child_pid) {
249 kill(session->child_pid, SIGTERM);
250 waitpid(session->child_pid, NULL, 0);
251 session->child_pid = 0;
257 gint session_send_msg(Session *session, SessionMsgType type, const gchar *msg)
265 case SESSION_MSG_NORMAL:
266 prefix = "MESSAGE"; break;
267 case SESSION_MSG_SEND_DATA:
268 prefix = "SENDDATA"; break;
269 case SESSION_MSG_CONTROL:
270 prefix = "CONTROL"; break;
271 case SESSION_MSG_ERROR:
272 prefix = "ERROR"; break;
277 str = g_strdup_printf("%s %s\n", prefix, msg);
278 g_print("%s: sending message: %s", session->child_pid == 0 ? "child" : "parent", str);
282 if (g_io_channel_write(session->write_ch, str, size,
284 != G_IO_ERROR_NONE || bytes_written == 0) {
285 g_warning("%s: sending message failed.\n",
286 session->child_pid == 0 ? "child" : "parent");
289 size -= bytes_written;
295 static gchar *session_recv_msg(Session *session)
303 if (g_io_channel_read(session->read_ch, buf, sizeof(buf) - 1,
305 != G_IO_ERROR_NONE || bytes_read == 0) {
306 g_warning("%s: receiving message failed.\n",
307 session->child_pid == 0 ? "child" : "parent");
314 buf[bytes_read] = '\0';
319 str = g_realloc(str, size);
322 if (str[size - 2] == '\n') {
323 str[size - 2] = '\0';
325 g_print("%s: received message: %s\n", session->child_pid == 0 ? "child" : "parent", str);
335 gint session_start_tls(Session *session)
339 session_send_msg(session, SESSION_MSG_CONTROL, "STARTTLS");
340 ctl_msg = session_recv_msg(session);
341 if (!ctl_msg || strcmp(ctl_msg, "CONTROL STARTTLSOK") != 0) {
351 gint session_send_data(Session *session, const guchar *data, guint size)
357 session_send_msg(session, SESSION_MSG_SEND_DATA, itos(size));
358 if ((msg = session_recv_msg(session)) == NULL)
363 if ((err = g_io_channel_write(session->write_ch, (guchar *)data,
364 size, &bytes_written))
365 != G_IO_ERROR_NONE || bytes_written == 0) {
366 g_warning("%s: sending data failed: %d\n",
367 session->child_pid == 0 ? "child" : "parent",
371 size -= bytes_written;
372 g_print("%s: sent %d bytes of data\n", session->child_pid == 0 ? "child" : "parent", bytes_written);
378 static guchar *session_read_data(Session *session, guint size)
385 cur = data = g_malloc(size);
388 if ((err = g_io_channel_read(session->read_ch, cur, size,
390 != G_IO_ERROR_NONE || bytes_read == 0) {
391 g_warning("%s: reading data failed: %d\n",
392 session->child_pid == 0 ? "child" : "parent",
399 g_print("%s: received %d bytes of data\n", session->child_pid == 0 ? "child" : "parent", bytes_read);
405 #define MAX_CHUNK_SIZE 4096
407 static gint session_send_data_to_sock(Session *session, const guchar *data,
410 const guchar *cur = data;
412 gint total_write_len = 0;
418 bytes_written = sock_write(session->sock, cur,
419 MIN(left, MAX_CHUNK_SIZE));
420 if (bytes_written <= 0)
422 left -= bytes_written;
423 cur += bytes_written;
424 total_write_len += bytes_written;
426 g_snprintf(buf, sizeof(buf), "DATASENDINPROG %d %d",
427 total_write_len, size);
428 session_send_msg(session, SESSION_MSG_CONTROL, buf);
429 if ((msg = session_recv_msg(session)) == NULL)
438 static guchar *session_recv_data_from_sock(Session *session, guint size)
443 gint total_read_len = 0;
448 cur = data = g_malloc(size);
451 bytes_read = sock_read(session->sock, cur, left);
452 if (bytes_read <= 0) {
456 g_print("child: received %d bytes of data from sock\n", bytes_read);
459 total_read_len += bytes_read;
460 g_snprintf(buf, sizeof(buf), "DATARECVINPROG %d %d",
461 total_read_len, size);
462 session_send_msg(session, SESSION_MSG_CONTROL, buf);
463 if ((msg = session_recv_msg(session)) == NULL) {
473 static SessionMsgType session_get_msg_type(const gchar *str)
475 if (!strncmp(str, "MESSAGE ", 8))
476 return SESSION_MSG_NORMAL;
477 else if (!strncmp(str, "SENDDATA ", 9))
478 return SESSION_MSG_SEND_DATA;
479 else if (!strncmp(str, "RECVDATA ", 9))
480 return SESSION_MSG_RECV_DATA;
481 else if (!strncmp(str, "CONTROL ", 8))
482 return SESSION_MSG_CONTROL;
483 else if (!strncmp(str, "ERROR ", 6))
484 return SESSION_MSG_ERROR;
486 return SESSION_MSG_UNKNOWN;
489 gboolean session_parent_input_cb(GIOChannel *source, GIOCondition condition,
492 Session *session = SESSION(data);
501 if ((msg = session_recv_msg(session)) == NULL) {
502 session->state = SESSION_ERROR;
506 switch (session_get_msg_type(msg)) {
507 case SESSION_MSG_NORMAL:
508 msg_data = msg + strlen("MESSAGE ");
509 ret = session->recv_msg(session, msg_data);
511 session->recv_msg_notify(session, msg_data,
512 session->recv_msg_notify_data);
514 session_send_msg(session, SESSION_MSG_CONTROL,
517 session->state = SESSION_ERROR;
522 case SESSION_MSG_SEND_DATA:
523 msg_data = msg + strlen("SENDDATA ");
524 size = atoi(msg_data);
525 session_send_msg(session, SESSION_MSG_CONTROL, "ACCEPTDATA");
526 recv_data = session_read_data(session, size);
528 session->state = SESSION_ERROR;
532 session->recv_data_finished(session, recv_data, size);
535 case SESSION_MSG_RECV_DATA:
537 case SESSION_MSG_CONTROL:
538 msg_data = msg + strlen("CONTROL ");
539 if (!strncmp(msg_data, "DATARECVINPROG ", 15)) {
540 ret = sscanf(msg_data,
541 "DATARECVINPROG %d %d", &len, &total);
543 g_warning("wrong control message: %s\n", msg);
544 session->state = SESSION_ERROR;
548 session_send_msg(session, SESSION_MSG_CONTROL,
550 session->recv_data_progressive_notify
551 (session, len, total,
552 session->recv_data_progressive_notify_data);
553 } else if (!strncmp(msg_data, "DATASENDINPROG ", 15)) {
554 ret = sscanf(msg_data,
555 "DATASENDINPROG %d %d", &len, &total);
557 g_warning("wrong control message: %s\n", msg);
558 session->state = SESSION_ERROR;
562 session_send_msg(session, SESSION_MSG_CONTROL,
564 session->send_data_progressive_notify
565 (session, len, total,
566 session->send_data_progressive_notify_data);
567 } else if (!strncmp(msg_data, "DATASENT ", 9)) {
568 len = atoi(msg_data + 9);
569 ret = session->send_data_finished(session, len);
570 session->send_data_notify
571 (session, len, session->send_data_notify_data);
572 } else if (!strcmp(msg_data, "DISCONNECTED")) {
573 session->state = SESSION_DISCONNECTED;
577 g_warning("wrong control message: %s\n", msg);
578 session->state = SESSION_ERROR;
583 case SESSION_MSG_ERROR:
585 g_warning("error from child: %s\n", msg + strlen("ERROR "));
586 session->state = SESSION_ERROR;
595 gboolean session_child_input(Session *session)
605 if ((msg = session_recv_msg(session)) == NULL) {
606 session_send_msg(session, SESSION_MSG_ERROR,
607 "receiving message failed.");
608 session_close(session);
609 session->state = SESSION_ERROR;
613 switch (session_get_msg_type(msg)) {
614 case SESSION_MSG_NORMAL:
615 msg_data = msg + strlen("MESSAGE ");
616 session->state = SESSION_SEND;
617 sock_puts(session->sock, msg_data);
618 session->state = SESSION_RECV;
619 str = sock_getline(session->sock);
621 session_send_msg(session, SESSION_MSG_ERROR,
622 "receiving message failed.");
623 session_close(session);
624 session->state = SESSION_ERROR;
629 session_send_msg(session, SESSION_MSG_NORMAL, str);
632 case SESSION_MSG_SEND_DATA:
633 msg_data = msg + strlen("SENDDATA ");
634 size = atoi(msg_data);
635 session_send_msg(session, SESSION_MSG_CONTROL, "ACCEPTDATA");
636 send_data = session_read_data(session, size);
638 session_send_msg(session, SESSION_MSG_ERROR,
639 "sending data failed.");
640 session_close(session);
641 session->state = SESSION_ERROR;
645 session->state = SESSION_SEND;
646 if (session_send_data_to_sock(session, send_data, size) < 0) {
647 session_send_msg(session, SESSION_MSG_ERROR,
648 "sending data failed.");
649 session_close(session);
650 session->state = SESSION_ERROR;
656 g_snprintf(buf, sizeof(buf), "DATASENT %d", size);
657 session_send_msg(session, SESSION_MSG_CONTROL, buf);
659 case SESSION_MSG_RECV_DATA:
660 msg_data = msg + strlen("RECVDATA ");
661 size = atoi(msg_data);
662 session->state = SESSION_RECV;
663 recv_data = session_recv_data_from_sock(session, size);
665 session_send_msg(session, SESSION_MSG_ERROR,
666 "receiving data failed.");
667 session_close(session);
668 session->state = SESSION_ERROR;
672 if (session_send_data(session, recv_data, size) < 0) {
673 session_close(session);
674 session->state = SESSION_ERROR;
681 case SESSION_MSG_CONTROL:
682 msg_data = msg + strlen("CONTROL ");
683 if (!strcmp(msg_data, "CONTINUE")) {
684 session->state = SESSION_RECV;
685 str = sock_getline(session->sock);
687 session_send_msg(session, SESSION_MSG_ERROR,
688 "receiving message failed.");
689 session_close(session);
690 session->state = SESSION_ERROR;
695 session_send_msg(session, SESSION_MSG_NORMAL, str);
699 } else if (!strcmp(msg_data, "STARTTLS")) {
700 if (!ssl_init_socket_with_method(session->sock,
702 session_send_msg(session, SESSION_MSG_ERROR,
703 "can't start TLS session.");
704 session_close(session);
705 session->state = SESSION_ERROR;
709 session_send_msg(session, SESSION_MSG_CONTROL,
713 } else if (!strcmp(msg_data, "DISCONNECT")) {
714 sock_close(session->sock);
715 session->sock = NULL;
716 session->state = SESSION_DISCONNECTED;
717 session_send_msg(session, SESSION_MSG_CONTROL,
722 session_send_msg(session, SESSION_MSG_ERROR,
723 "wrong control message.");
724 session_close(session);
725 session->state = SESSION_ERROR;
730 case SESSION_MSG_ERROR:
732 session_send_msg(session, SESSION_MSG_ERROR,
733 "error received from parent.");
734 session_close(session);
735 session->state = SESSION_ERROR;