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>
40 static gint session_close (Session *session);
42 static gchar *session_recv_msg (Session *session);
44 static guchar *session_read_data (Session *session,
47 static gint session_send_data_to_sock (Session *session,
50 static guchar *session_recv_data_from_sock (Session *session,
53 static guchar *session_recv_data_from_sock_unescape (Session *session,
57 gboolean session_parent_input_cb (GIOChannel *source,
58 GIOCondition condition,
61 gboolean session_child_input (Session *session);
65 *\brief init session members to zero
67 *\param session to be initialized
69 void session_init(Session *session)
74 session->server = NULL;
76 session->state = SESSION_READY;
77 session->last_access_time = time(NULL);
80 session->read_ch = NULL;
81 session->write_ch = NULL;
85 *\brief Set up parent and child process
86 * Childloop: Read commands from parent,
87 * send to server, get answer, pass to parent
89 *\param session Contains session information
90 * server to connect to
94 * -1 : pipe / fork errors (parent)
95 * 1 : connection error (child)
97 gint session_connect(Session *session, const gchar *server, gushort port)
100 gint pipe_fds1[2], pipe_fds2[2];
104 session->server = g_strdup(server);
105 session->port = port;
107 if (pipe(pipe_fds1) < 0) {
111 if (pipe(pipe_fds2) < 0) {
118 if ((pid = fork()) < 0) {
124 session->child_pid = pid;
125 session->read_ch = g_io_channel_unix_new(pipe_fds2[0]);
126 session->write_ch = g_io_channel_unix_new(pipe_fds1[1]);
129 session->read_tag = g_io_add_watch(session->read_ch, G_IO_IN,
130 session_parent_input_cb,
137 session->read_ch = g_io_channel_unix_new(pipe_fds1[0]);
138 session->write_ch = g_io_channel_unix_new(pipe_fds2[1]);
142 g_print("child: connecting to %s:%d ...\n", server, port);
144 if ((sock = sock_connect(server, port)) == NULL) {
145 session_send_msg(session, SESSION_MSG_ERROR,
146 "can't connect to server.");
147 session_close(session);
152 if (session->ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
153 session_send_msg(session, SESSION_MSG_ERROR,
154 "can't initialize SSL.");
155 session_close(session);
160 g_print("child: connected\n");
162 session->sock = sock;
163 session->state = SESSION_RECV;
165 if ((str = sock_getline(sock)) == NULL) {
166 session_send_msg(session, SESSION_MSG_ERROR,
167 "can't get server response.");
168 session_close(session);
172 session_send_msg(session, SESSION_MSG_NORMAL, str);
175 while (session_child_input(session) == TRUE)
178 session_close(session);
180 g_print("child: disconnected\n");
186 *\brief child and parent: send DISCONNECT message to other process
188 *\param session Contains session information
192 gint session_disconnect(Session *session)
194 g_print("%s: session_disconnect()\n", session->child_pid == 0 ? "child" : "parent");
195 session_send_msg(session, SESSION_MSG_CONTROL, "DISCONNECT");
202 *\param session Contains session information
204 void session_destroy(Session *session)
206 g_return_if_fail(session != NULL);
207 g_return_if_fail(session->destroy != NULL);
209 g_print("session_destroy()\n");
210 session_close(session);
211 session->destroy(session);
212 g_free(session->server);
216 void session_set_recv_message_notify(Session *session,
217 RecvMsgNotify notify_func, gpointer data)
219 session->recv_msg_notify = notify_func;
220 session->recv_msg_notify_data = data;
223 void session_set_recv_data_progressive_notify
225 RecvDataProgressiveNotify notify_func,
228 session->recv_data_progressive_notify = notify_func,
229 session->recv_data_progressive_notify_data = data;
232 void session_set_recv_data_notify(Session *session, RecvDataNotify notify_func,
235 session->recv_data_notify = notify_func;
236 session->recv_data_notify_data = data;
239 void session_set_send_data_progressive_notify
241 SendDataProgressiveNotify notify_func,
244 session->send_data_progressive_notify = notify_func;
245 session->send_data_progressive_notify_data = data;
248 void session_set_send_data_notify(Session *session, SendDataNotify notify_func,
251 session->send_data_notify = notify_func;
252 session->send_data_notify_data = data;
256 *\brief child and parent cleanup (child closes first)
258 *\param session Contains session information
262 static gint session_close(Session *session)
264 g_return_val_if_fail(session != NULL, -1);
266 g_print("%s: session_close()\n", session->child_pid == 0 ? "child" : "parent");
268 if (session->read_tag > 0) {
269 g_source_remove(session->read_tag);
270 session->read_tag = 0;
273 if (session->read_ch) {
274 g_io_channel_close(session->read_ch);
275 g_io_channel_unref(session->read_ch);
276 session->read_ch = NULL;
278 if (session->write_ch) {
279 g_io_channel_close(session->write_ch);
280 g_io_channel_unref(session->write_ch);
281 session->write_ch = NULL;
285 sock_close(session->sock);
286 session->sock = NULL;
287 session->state = SESSION_DISCONNECTED;
290 if (session->child_pid) {
291 kill(session->child_pid, SIGTERM);
292 waitpid(session->child_pid, NULL, 0);
293 session->child_pid = 0;
300 *\brief child and parent: send control message to other process
302 *\param session Contains session information
303 * type Kind of data (commands or message data)
309 gint session_send_msg(Session *session, SessionMsgType type, const gchar *msg)
317 case SESSION_MSG_NORMAL:
318 prefix = "MESSAGE"; break;
319 case SESSION_MSG_SEND_DATA:
320 prefix = "SENDDATA"; break;
321 case SESSION_MSG_RECV_DATA:
322 prefix = "RECVDATA"; break;
323 case SESSION_MSG_CONTROL:
324 prefix = "CONTROL"; break;
325 case SESSION_MSG_ERROR:
326 prefix = "ERROR"; break;
331 str = g_strdup_printf("%s %s\n", prefix, msg);
332 /* g_print("%s: sending message: %s", session->child_pid == 0 ? "child" : "parent", str); */
336 if (g_io_channel_write(session->write_ch, str, size,
338 != G_IO_ERROR_NONE || bytes_written == 0) {
339 g_warning("%s: sending message failed.\n",
340 session->child_pid == 0 ? "child" : "parent");
343 size -= bytes_written;
350 *\brief child and parent receive function
352 *\param session Contains session information
354 *\return Message read by current session
356 static gchar *session_recv_msg(Session *session)
364 if (g_io_channel_read(session->read_ch, buf, sizeof(buf) - 1,
366 != G_IO_ERROR_NONE || bytes_read == 0) {
367 g_warning("%s: receiving message failed.\n",
368 session->child_pid == 0 ? "child" : "parent");
375 buf[bytes_read] = '\0';
380 str = g_realloc(str, size);
383 if (str[size - 2] == '\n') {
384 str[size - 2] = '\0';
386 g_print("%s: received message: %s\n", session->child_pid == 0 ? "child" : "parent", str);
396 gint session_start_tls(Session *session)
400 session_send_msg(session, SESSION_MSG_CONTROL, "STARTTLS");
401 ctl_msg = session_recv_msg(session);
402 if (!ctl_msg || strcmp(ctl_msg, "CONTROL STARTTLSOK") != 0) {
413 *\brief parent (child?): send data to other process
415 *\param session Contains session information
422 gint session_send_data(Session *session, const guchar *data, guint size)
428 session_send_msg(session, SESSION_MSG_SEND_DATA, itos(size));
429 if ((msg = session_recv_msg(session)) == NULL)
434 if ((err = g_io_channel_write(session->write_ch, (guchar *)data,
435 size, &bytes_written))
436 != G_IO_ERROR_NONE || bytes_written == 0) {
437 g_warning("%s: sending data failed: %d\n",
438 session->child_pid == 0 ? "child" : "parent",
442 size -= bytes_written;
443 g_print("%s: sent %d bytes of data\n", session->child_pid == 0 ? "child" : "parent", bytes_written);
449 gint session_recv_data(Session *session, guint size, gboolean unescape_dot)
454 g_snprintf(buf, sizeof(buf), "%d UNESCAPE", size);
455 session_send_msg(session, SESSION_MSG_RECV_DATA, buf);
457 session_send_msg(session, SESSION_MSG_RECV_DATA, itos(size));
462 *\brief child (parent?): read data from other process
464 *\param session Contains session information
467 *\return data read from session
469 static guchar *session_read_data(Session *session, guint size)
476 cur = data = g_malloc(size);
479 if ((err = g_io_channel_read(session->read_ch, cur, size,
481 != G_IO_ERROR_NONE || bytes_read == 0) {
482 g_warning("%s: reading data failed: %d\n",
483 session->child_pid == 0 ? "child" : "parent",
490 g_print("%s: received %d bytes of data\n", session->child_pid == 0 ? "child" : "parent", bytes_read);
496 #define MAX_CHUNK_SIZE 4096
499 *\brief child: Send session data to server
501 *\param session Contains session information
502 * data Data to send to server
508 static gint session_send_data_to_sock(Session *session, const guchar *data,
511 const guchar *cur = data;
513 gint total_write_len = 0;
517 struct timeval tv_prev, tv_cur;
519 gettimeofday(&tv_prev, NULL);
522 bytes_written = sock_write(session->sock, cur,
523 MIN(left, MAX_CHUNK_SIZE));
524 if (bytes_written <= 0)
526 left -= bytes_written;
527 cur += bytes_written;
528 total_write_len += bytes_written;
532 gettimeofday(&tv_cur, NULL);
533 if (tv_cur.tv_sec - tv_prev.tv_sec > 0 ||
534 tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) {
535 g_snprintf(buf, sizeof(buf), "DATASENDINPROG %d %d",
536 total_write_len, size);
537 session_send_msg(session, SESSION_MSG_CONTROL, buf);
538 if ((msg = session_recv_msg(session)) == NULL)
541 gettimeofday(&tv_prev, NULL);
549 *\brief child: Read answer/data from server
551 *\param session Contains session information
552 * size Max bytes to receive
554 *\return Server answer
556 static guchar *session_recv_data_from_sock(Session *session, guint size)
561 gint total_read_len = 0;
565 struct timeval tv_prev, tv_cur;
567 gettimeofday(&tv_prev, NULL);
569 cur = data = g_malloc(size);
572 bytes_read = sock_read(session->sock, cur, left);
573 if (bytes_read <= 0) {
577 g_print("child: received %d bytes of data from sock\n", bytes_read);
580 total_read_len += bytes_read;
584 gettimeofday(&tv_cur, NULL);
585 if (tv_cur.tv_sec - tv_prev.tv_sec > 0 ||
586 tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) {
587 g_snprintf(buf, sizeof(buf), "DATARECVINPROG %d %d",
588 total_read_len, size);
589 session_send_msg(session, SESSION_MSG_CONTROL, buf);
590 if ((msg = session_recv_msg(session)) == NULL) {
595 gettimeofday(&tv_prev, NULL);
602 static guchar *session_recv_data_from_sock_unescape(Session *session,
611 struct timeval tv_prev, tv_cur;
613 gettimeofday(&tv_prev, NULL);
615 data = g_string_sized_new(size + 1);
619 bytes_read = sock_gets(session->sock, buf, sizeof(buf));
620 if (bytes_read <= 0) {
621 g_string_free(data, TRUE);
625 if (buf[0] == '.' && buf[1] == '\r' && buf[2] == '\n')
627 if (buf[0] == '.' && buf[1] == '.')
628 g_string_append(data, buf + 1);
630 g_string_append(data, buf);
632 gettimeofday(&tv_cur, NULL);
633 if (tv_cur.tv_sec - tv_prev.tv_sec > 0 ||
634 tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) {
635 g_snprintf(buf, sizeof(buf), "DATARECVINPROG %d %d",
636 data->len, MAX(data->len, size));
637 session_send_msg(session, SESSION_MSG_CONTROL, buf);
638 if ((msg = session_recv_msg(session)) == NULL) {
639 g_string_free(data, TRUE);
643 gettimeofday(&tv_prev, NULL);
647 ret_data = data->str;
648 *actual_size = data->len;
649 g_string_free(data, FALSE);
655 *\brief Return if message is an internal command or server data
657 *\param str Message to analyze
659 *\return Type of message
661 static SessionMsgType session_get_msg_type(const gchar *str)
663 if (!strncmp(str, "MESSAGE ", 8))
664 return SESSION_MSG_NORMAL;
665 else if (!strncmp(str, "SENDDATA ", 9))
666 return SESSION_MSG_SEND_DATA;
667 else if (!strncmp(str, "RECVDATA ", 9))
668 return SESSION_MSG_RECV_DATA;
669 else if (!strncmp(str, "CONTROL ", 8))
670 return SESSION_MSG_CONTROL;
671 else if (!strncmp(str, "ERROR ", 6))
672 return SESSION_MSG_ERROR;
674 return SESSION_MSG_UNKNOWN;
678 *\brief parent: Received data from child
680 *\param source Channel watching child pipe
681 * condition Unused (IN, HUP, OUT)
682 * data Contains session information
684 *\return FALSE to remove watching channel
686 gboolean session_parent_input_cb(GIOChannel *source, GIOCondition condition,
689 Session *session = SESSION(data);
698 if ((msg = session_recv_msg(session)) == NULL) {
699 session->state = SESSION_ERROR;
703 switch (session_get_msg_type(msg)) {
704 case SESSION_MSG_NORMAL:
705 msg_data = msg + strlen("MESSAGE ");
706 ret = session->recv_msg(session, msg_data);
707 session->recv_msg_notify(session, msg_data,
708 session->recv_msg_notify_data);
710 session_send_msg(session, SESSION_MSG_CONTROL,
713 session->state = SESSION_ERROR;
718 case SESSION_MSG_SEND_DATA:
719 msg_data = msg + strlen("SENDDATA ");
720 size = atoi(msg_data);
721 session_send_msg(session, SESSION_MSG_CONTROL, "ACCEPTDATA");
722 recv_data = session_read_data(session, size);
724 session->state = SESSION_ERROR;
728 ret = session->recv_data_finished(session, recv_data, size);
730 session->recv_data_notify(session, size,
731 session->recv_data_notify_data);
733 session_send_msg(session, SESSION_MSG_CONTROL,
736 session->state = SESSION_ERROR;
741 case SESSION_MSG_RECV_DATA:
743 case SESSION_MSG_CONTROL:
744 msg_data = msg + strlen("CONTROL ");
745 if (!strncmp(msg_data, "DATARECVINPROG ", 15)) {
746 ret = sscanf(msg_data,
747 "DATARECVINPROG %d %d", &len, &total);
749 g_warning("wrong control message: %s\n", msg);
750 session->state = SESSION_ERROR;
754 session_send_msg(session, SESSION_MSG_CONTROL,
756 session->recv_data_progressive_notify
757 (session, len, total,
758 session->recv_data_progressive_notify_data);
759 } else if (!strncmp(msg_data, "DATASENDINPROG ", 15)) {
760 ret = sscanf(msg_data,
761 "DATASENDINPROG %d %d", &len, &total);
763 g_warning("wrong control message: %s\n", msg);
764 session->state = SESSION_ERROR;
768 session_send_msg(session, SESSION_MSG_CONTROL,
770 session->send_data_progressive_notify
771 (session, len, total,
772 session->send_data_progressive_notify_data);
773 } else if (!strncmp(msg_data, "DATASENT ", 9)) {
774 len = atoi(msg_data + 9);
775 ret = session->send_data_finished(session, len);
776 session->send_data_notify
777 (session, len, session->send_data_notify_data);
778 } else if (!strcmp(msg_data, "DISCONNECTED")) {
779 session->state = SESSION_DISCONNECTED;
783 g_warning("wrong control message: %s\n", msg);
784 session->state = SESSION_ERROR;
789 case SESSION_MSG_ERROR:
791 g_warning("error from child: %s\n", msg + strlen("ERROR "));
792 session->state = SESSION_ERROR;
802 *\brief child: Receive control message from parent,
803 * transfer data from/to server
805 *\param session Contains session information
807 *\return TRUE if more data is available
809 gboolean session_child_input(Session *session)
820 if ((msg = session_recv_msg(session)) == NULL) {
821 session_send_msg(session, SESSION_MSG_ERROR,
822 "receiving message failed.");
823 session_close(session);
824 session->state = SESSION_ERROR;
828 switch (session_get_msg_type(msg)) {
829 case SESSION_MSG_NORMAL:
830 msg_data = msg + strlen("MESSAGE ");
831 session->state = SESSION_SEND;
832 sock_puts(session->sock, msg_data);
833 session->state = SESSION_RECV;
834 str = sock_getline(session->sock);
836 session_send_msg(session, SESSION_MSG_ERROR,
837 "receiving message failed.");
838 session_close(session);
839 session->state = SESSION_ERROR;
844 session_send_msg(session, SESSION_MSG_NORMAL, str);
847 case SESSION_MSG_SEND_DATA:
848 msg_data = msg + strlen("SENDDATA ");
849 size = atoi(msg_data);
850 session_send_msg(session, SESSION_MSG_CONTROL, "ACCEPTDATA");
851 send_data = session_read_data(session, size);
853 session_send_msg(session, SESSION_MSG_ERROR,
854 "sending data failed.");
855 session_close(session);
856 session->state = SESSION_ERROR;
860 session->state = SESSION_SEND;
861 if (session_send_data_to_sock(session, send_data, size) < 0) {
862 session_send_msg(session, SESSION_MSG_ERROR,
863 "sending data failed.");
864 session_close(session);
865 session->state = SESSION_ERROR;
871 g_snprintf(buf, sizeof(buf), "DATASENT %d", size);
872 session_send_msg(session, SESSION_MSG_CONTROL, buf);
874 case SESSION_MSG_RECV_DATA:
875 msg_data = msg + strlen("RECVDATA ");
876 size = atoi(msg_data);
877 session->state = SESSION_RECV;
878 if (strstr(msg_data, "UNESCAPE") != NULL) {
879 recv_data = session_recv_data_from_sock_unescape
880 (session, size, &actual_size);
883 recv_data = session_recv_data_from_sock(session, size);
885 session_send_msg(session, SESSION_MSG_ERROR,
886 "receiving data failed.");
887 session_close(session);
888 session->state = SESSION_ERROR;
892 if (session_send_data(session, recv_data, size) < 0) {
893 session_close(session);
894 session->state = SESSION_ERROR;
901 case SESSION_MSG_CONTROL:
902 msg_data = msg + strlen("CONTROL ");
903 if (!strcmp(msg_data, "CONTINUE")) {
904 session->state = SESSION_RECV;
905 str = sock_getline(session->sock);
907 session_send_msg(session, SESSION_MSG_ERROR,
908 "receiving message failed.");
909 session_close(session);
910 session->state = SESSION_ERROR;
915 session_send_msg(session, SESSION_MSG_NORMAL, str);
919 } else if (!strcmp(msg_data, "STARTTLS")) {
920 if (!ssl_init_socket_with_method(session->sock,
922 session_send_msg(session, SESSION_MSG_ERROR,
923 "can't start TLS session.");
924 session_close(session);
925 session->state = SESSION_ERROR;
929 session_send_msg(session, SESSION_MSG_CONTROL,
933 } else if (!strcmp(msg_data, "DISCONNECT")) {
934 sock_close(session->sock);
935 session->sock = NULL;
936 session->state = SESSION_DISCONNECTED;
937 session_send_msg(session, SESSION_MSG_CONTROL,
942 session_send_msg(session, SESSION_MSG_ERROR,
943 "wrong control message.");
944 session_close(session);
945 session->state = SESSION_ERROR;
950 case SESSION_MSG_ERROR:
952 session_send_msg(session, SESSION_MSG_ERROR,
953 "error received from parent.");
954 session_close(session);
955 session->state = SESSION_ERROR;