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 debug_print("session: 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 debug_print("session: 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 debug_print("session: 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 debug_print("session: %s: session_disconnect()\n",
195 session->child_pid == 0 ? "child" : "parent");
196 session_send_msg(session, SESSION_MSG_CONTROL, "DISCONNECT");
203 *\param session Contains session information
205 void session_destroy(Session *session)
207 g_return_if_fail(session != NULL);
208 g_return_if_fail(session->destroy != NULL);
210 debug_print("session: session_destroy()\n");
211 session_close(session);
212 session->destroy(session);
213 g_free(session->server);
217 void session_set_recv_message_notify(Session *session,
218 RecvMsgNotify notify_func, gpointer data)
220 session->recv_msg_notify = notify_func;
221 session->recv_msg_notify_data = data;
224 void session_set_recv_data_progressive_notify
226 RecvDataProgressiveNotify notify_func,
229 session->recv_data_progressive_notify = notify_func,
230 session->recv_data_progressive_notify_data = data;
233 void session_set_recv_data_notify(Session *session, RecvDataNotify notify_func,
236 session->recv_data_notify = notify_func;
237 session->recv_data_notify_data = data;
240 void session_set_send_data_progressive_notify
242 SendDataProgressiveNotify notify_func,
245 session->send_data_progressive_notify = notify_func;
246 session->send_data_progressive_notify_data = data;
249 void session_set_send_data_notify(Session *session, SendDataNotify notify_func,
252 session->send_data_notify = notify_func;
253 session->send_data_notify_data = data;
257 *\brief child and parent cleanup (child closes first)
259 *\param session Contains session information
263 static gint session_close(Session *session)
265 g_return_val_if_fail(session != NULL, -1);
267 debug_print("session: %s: session_close()\n",
268 session->child_pid == 0 ? "child" : "parent");
270 if (session->read_tag > 0) {
271 g_source_remove(session->read_tag);
272 session->read_tag = 0;
275 if (session->read_ch) {
276 g_io_channel_close(session->read_ch);
277 g_io_channel_unref(session->read_ch);
278 session->read_ch = NULL;
281 if (session->write_ch) {
282 g_io_channel_close(session->write_ch);
283 g_io_channel_unref(session->write_ch);
284 session->write_ch = NULL;
288 sock_close(session->sock);
289 session->sock = NULL;
290 session->state = SESSION_DISCONNECTED;
293 if (session->child_pid) {
294 if (session->state != SESSION_DISCONNECTED)
295 kill(session->child_pid, SIGTERM);
296 waitpid(session->child_pid, NULL, 0);
297 session->child_pid = 0;
304 *\brief child and parent: send control message to other process
306 *\param session Contains session information
307 * type Kind of data (commands or message data)
313 gint session_send_msg(Session *session, SessionMsgType type, const gchar *msg)
322 case SESSION_MSG_NORMAL:
323 prefix = "MESSAGE"; break;
324 case SESSION_MSG_SEND_DATA:
325 prefix = "SENDDATA"; break;
326 case SESSION_MSG_RECV_DATA:
327 prefix = "RECVDATA"; break;
328 case SESSION_MSG_CONTROL:
329 prefix = "CONTROL"; break;
330 case SESSION_MSG_ERROR:
331 prefix = "ERROR"; break;
336 cur = str = g_strdup_printf("%s %s\n", prefix, msg);
340 if (g_io_channel_write(session->write_ch, cur, size,
342 != G_IO_ERROR_NONE || bytes_written == 0) {
343 g_warning("%s: sending message failed.\n",
344 session->child_pid == 0 ? "child" : "parent");
348 size -= bytes_written;
349 cur += bytes_written;
358 *\brief child and parent receive function
360 *\param session Contains session information
362 *\return Message read by current session
364 static gchar *session_recv_msg(Session *session)
372 if (g_io_channel_read(session->read_ch, buf, sizeof(buf) - 1,
374 != G_IO_ERROR_NONE || bytes_read == 0) {
375 g_warning("%s: receiving message failed.\n",
376 session->child_pid == 0 ? "child" : "parent");
383 buf[bytes_read] = '\0';
388 str = g_realloc(str, size);
391 if (str[size - 2] == '\n') {
392 str[size - 2] = '\0';
401 gint session_start_tls(Session *session)
405 session_send_msg(session, SESSION_MSG_CONTROL, "STARTTLS");
406 ctl_msg = session_recv_msg(session);
407 if (!ctl_msg || strcmp(ctl_msg, "CONTROL STARTTLSOK") != 0) {
418 *\brief parent (child?): send data to other process
420 *\param session Contains session information
427 gint session_send_data(Session *session, const guchar *data, guint size)
430 const guchar *cur = data;
434 session_send_msg(session, SESSION_MSG_SEND_DATA, itos(size));
435 if ((msg = session_recv_msg(session)) == NULL)
440 if ((err = g_io_channel_write(session->write_ch, (guchar *)cur,
441 size, &bytes_written))
442 != G_IO_ERROR_NONE || bytes_written == 0) {
443 g_warning("%s: sending data failed: %d\n",
444 session->child_pid == 0 ? "child" : "parent",
448 size -= bytes_written;
449 cur += bytes_written;
450 debug_print("session: %s: sent %d bytes of data\n",
451 session->child_pid == 0 ? "child" : "parent",
458 gint session_recv_data(Session *session, guint size, gboolean unescape_dot)
463 g_snprintf(buf, sizeof(buf), "%d UNESCAPE", size);
464 session_send_msg(session, SESSION_MSG_RECV_DATA, buf);
466 session_send_msg(session, SESSION_MSG_RECV_DATA, itos(size));
471 *\brief child (parent?): read data from other process
473 *\param session Contains session information
476 *\return data read from session
478 static guchar *session_read_data(Session *session, guint size)
485 cur = data = g_malloc(size);
488 if ((err = g_io_channel_read(session->read_ch, cur, size,
490 != G_IO_ERROR_NONE || bytes_read == 0) {
491 g_warning("%s: reading data failed: %d\n",
492 session->child_pid == 0 ? "child" : "parent",
499 debug_print("session: %s: received %d bytes of data\n",
500 session->child_pid == 0 ? "child" : "parent",
507 #define MAX_CHUNK_SIZE 4096
510 *\brief child: Send session data to server
512 *\param session Contains session information
513 * data Data to send to server
519 static gint session_send_data_to_sock(Session *session, const guchar *data,
522 const guchar *cur = data;
524 gint total_write_len = 0;
528 struct timeval tv_prev, tv_cur;
530 gettimeofday(&tv_prev, NULL);
533 bytes_written = sock_write(session->sock, cur,
534 MIN(left, MAX_CHUNK_SIZE));
535 if (bytes_written <= 0)
537 left -= bytes_written;
538 cur += bytes_written;
539 total_write_len += bytes_written;
543 gettimeofday(&tv_cur, NULL);
544 if (tv_cur.tv_sec - tv_prev.tv_sec > 0 ||
545 tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) {
546 g_snprintf(buf, sizeof(buf), "DATASENDINPROG %d %d",
547 total_write_len, size);
548 session_send_msg(session, SESSION_MSG_CONTROL, buf);
549 if ((msg = session_recv_msg(session)) == NULL)
552 gettimeofday(&tv_prev, NULL);
560 *\brief child: Read answer/data from server
562 *\param session Contains session information
563 * size Max bytes to receive
565 *\return Server answer
567 static guchar *session_recv_data_from_sock(Session *session, guint size)
572 gint total_read_len = 0;
576 struct timeval tv_prev, tv_cur;
578 gettimeofday(&tv_prev, NULL);
580 cur = data = g_malloc(size);
583 bytes_read = sock_read(session->sock, cur, left);
584 if (bytes_read <= 0) {
588 debug_print("session: child: "
589 "received %d bytes of data from sock\n",
593 total_read_len += bytes_read;
597 gettimeofday(&tv_cur, NULL);
598 if (tv_cur.tv_sec - tv_prev.tv_sec > 0 ||
599 tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) {
600 g_snprintf(buf, sizeof(buf), "DATARECVINPROG %d %d",
601 total_read_len, size);
602 session_send_msg(session, SESSION_MSG_CONTROL, buf);
603 if ((msg = session_recv_msg(session)) == NULL) {
608 gettimeofday(&tv_prev, NULL);
615 static guchar *session_recv_data_from_sock_unescape(Session *session,
624 struct timeval tv_prev, tv_cur;
626 gettimeofday(&tv_prev, NULL);
628 data = g_string_sized_new(size + 1);
632 bytes_read = sock_gets(session->sock, buf, sizeof(buf));
633 if (bytes_read <= 0) {
634 g_string_free(data, TRUE);
638 if (buf[0] == '.' && buf[1] == '\r' && buf[2] == '\n')
640 if (buf[0] == '.' && buf[1] == '.')
641 g_string_append(data, buf + 1);
643 g_string_append(data, buf);
645 gettimeofday(&tv_cur, NULL);
646 if (tv_cur.tv_sec - tv_prev.tv_sec > 0 ||
647 tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) {
648 g_snprintf(buf, sizeof(buf), "DATARECVINPROG %d %d",
649 data->len, MAX(data->len, size));
650 session_send_msg(session, SESSION_MSG_CONTROL, buf);
651 if ((msg = session_recv_msg(session)) == NULL) {
652 g_string_free(data, TRUE);
656 gettimeofday(&tv_prev, NULL);
660 ret_data = data->str;
661 *actual_size = data->len;
662 g_string_free(data, FALSE);
668 *\brief Return if message is an internal command or server data
670 *\param str Message to analyze
672 *\return Type of message
674 static SessionMsgType session_get_msg_type(const gchar *str)
676 if (!strncmp(str, "MESSAGE ", 8))
677 return SESSION_MSG_NORMAL;
678 else if (!strncmp(str, "SENDDATA ", 9))
679 return SESSION_MSG_SEND_DATA;
680 else if (!strncmp(str, "RECVDATA ", 9))
681 return SESSION_MSG_RECV_DATA;
682 else if (!strncmp(str, "CONTROL ", 8))
683 return SESSION_MSG_CONTROL;
684 else if (!strncmp(str, "ERROR ", 6))
685 return SESSION_MSG_ERROR;
687 return SESSION_MSG_UNKNOWN;
691 *\brief parent: Received data from child
693 *\param source Channel watching child pipe
694 * condition Unused (IN, HUP, OUT)
695 * data Contains session information
697 *\return FALSE to remove watching channel
699 gboolean session_parent_input_cb(GIOChannel *source, GIOCondition condition,
702 Session *session = SESSION(data);
711 if ((msg = session_recv_msg(session)) == NULL) {
712 session->state = SESSION_ERROR;
716 switch (session_get_msg_type(msg)) {
717 case SESSION_MSG_NORMAL:
718 msg_data = msg + strlen("MESSAGE ");
719 ret = session->recv_msg(session, msg_data);
720 session->recv_msg_notify(session, msg_data,
721 session->recv_msg_notify_data);
723 session_send_msg(session, SESSION_MSG_CONTROL,
726 session->state = SESSION_ERROR;
731 case SESSION_MSG_SEND_DATA:
732 msg_data = msg + strlen("SENDDATA ");
733 size = atoi(msg_data);
734 session_send_msg(session, SESSION_MSG_CONTROL, "ACCEPTDATA");
735 recv_data = session_read_data(session, size);
737 session->state = SESSION_ERROR;
741 ret = session->recv_data_finished(session, recv_data, size);
743 session->recv_data_notify(session, size,
744 session->recv_data_notify_data);
746 session_send_msg(session, SESSION_MSG_CONTROL,
749 session->state = SESSION_ERROR;
754 case SESSION_MSG_RECV_DATA:
756 case SESSION_MSG_CONTROL:
757 msg_data = msg + strlen("CONTROL ");
758 if (!strncmp(msg_data, "DATARECVINPROG ", 15)) {
759 ret = sscanf(msg_data,
760 "DATARECVINPROG %d %d", &len, &total);
762 g_warning("wrong control message: %s\n", msg);
763 session->state = SESSION_ERROR;
767 session_send_msg(session, SESSION_MSG_CONTROL,
769 session->recv_data_progressive_notify
770 (session, len, total,
771 session->recv_data_progressive_notify_data);
772 } else if (!strncmp(msg_data, "DATASENDINPROG ", 15)) {
773 ret = sscanf(msg_data,
774 "DATASENDINPROG %d %d", &len, &total);
776 g_warning("wrong control message: %s\n", msg);
777 session->state = SESSION_ERROR;
781 session_send_msg(session, SESSION_MSG_CONTROL,
783 session->send_data_progressive_notify
784 (session, len, total,
785 session->send_data_progressive_notify_data);
786 } else if (!strncmp(msg_data, "DATASENT ", 9)) {
787 len = atoi(msg_data + 9);
788 ret = session->send_data_finished(session, len);
789 session->send_data_notify
790 (session, len, session->send_data_notify_data);
791 } else if (!strcmp(msg_data, "DISCONNECTED")) {
792 session->state = SESSION_DISCONNECTED;
796 g_warning("wrong control message: %s\n", msg);
797 session->state = SESSION_ERROR;
802 case SESSION_MSG_ERROR:
804 g_warning("error from child: %s\n", msg + strlen("ERROR "));
805 session->state = SESSION_ERROR;
815 *\brief child: Receive control message from parent,
816 * transfer data from/to server
818 *\param session Contains session information
820 *\return TRUE if more data is available
822 gboolean session_child_input(Session *session)
833 if ((msg = session_recv_msg(session)) == NULL) {
834 session_send_msg(session, SESSION_MSG_ERROR,
835 "receiving message failed.");
836 session->state = SESSION_ERROR;
840 switch (session_get_msg_type(msg)) {
841 case SESSION_MSG_NORMAL:
842 msg_data = msg + strlen("MESSAGE ");
843 session->state = SESSION_SEND;
844 sock_puts(session->sock, msg_data);
845 session->state = SESSION_RECV;
846 str = sock_getline(session->sock);
848 session_send_msg(session, SESSION_MSG_ERROR,
849 "receiving message failed.");
850 session->state = SESSION_ERROR;
855 session_send_msg(session, SESSION_MSG_NORMAL, str);
858 case SESSION_MSG_SEND_DATA:
859 msg_data = msg + strlen("SENDDATA ");
860 size = atoi(msg_data);
861 session_send_msg(session, SESSION_MSG_CONTROL, "ACCEPTDATA");
862 send_data = session_read_data(session, size);
864 session_send_msg(session, SESSION_MSG_ERROR,
865 "sending data failed.");
866 session->state = SESSION_ERROR;
870 session->state = SESSION_SEND;
871 if (session_send_data_to_sock(session, send_data, size) < 0) {
872 session_send_msg(session, SESSION_MSG_ERROR,
873 "sending data failed.");
874 session->state = SESSION_ERROR;
880 g_snprintf(buf, sizeof(buf), "DATASENT %d", size);
881 session_send_msg(session, SESSION_MSG_CONTROL, buf);
883 case SESSION_MSG_RECV_DATA:
884 msg_data = msg + strlen("RECVDATA ");
885 size = atoi(msg_data);
886 session->state = SESSION_RECV;
887 if (strstr(msg_data, "UNESCAPE") != NULL) {
888 recv_data = session_recv_data_from_sock_unescape
889 (session, size, &actual_size);
892 recv_data = session_recv_data_from_sock(session, size);
894 session_send_msg(session, SESSION_MSG_ERROR,
895 "receiving data failed.");
896 session->state = SESSION_ERROR;
900 if (session_send_data(session, recv_data, size) < 0) {
901 session->state = SESSION_ERROR;
908 case SESSION_MSG_CONTROL:
909 msg_data = msg + strlen("CONTROL ");
910 if (!strcmp(msg_data, "CONTINUE")) {
911 session->state = SESSION_RECV;
912 str = sock_getline(session->sock);
914 session_send_msg(session, SESSION_MSG_ERROR,
915 "receiving message failed.");
916 session->state = SESSION_ERROR;
921 session_send_msg(session, SESSION_MSG_NORMAL, str);
925 } else if (!strcmp(msg_data, "STARTTLS")) {
926 if (!ssl_init_socket_with_method(session->sock,
928 session_send_msg(session, SESSION_MSG_ERROR,
929 "can't start TLS session.");
930 session->state = SESSION_ERROR;
934 session_send_msg(session, SESSION_MSG_CONTROL,
938 } else if (!strcmp(msg_data, "DISCONNECT")) {
939 sock_close(session->sock);
940 session->sock = NULL;
941 session->state = SESSION_DISCONNECTED;
942 session_send_msg(session, SESSION_MSG_CONTROL,
947 session_send_msg(session, SESSION_MSG_ERROR,
948 "wrong control message.");
949 session->state = SESSION_ERROR;
954 case SESSION_MSG_ERROR:
956 session_send_msg(session, SESSION_MSG_ERROR,
957 "error received from parent.");
958 session->state = SESSION_ERROR;