Sync with HEAD 0.9.12cvs46
[claws.git] / src / common / session.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2004 Hiroyuki Yamamoto
4  *
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.
9  *
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.
14  *
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.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <unistd.h>
33 #include <sys/signal.h>
34 #include <sys/wait.h>
35 #include <sys/time.h>
36 #include <time.h>
37 #include <errno.h>
38
39 #include "session.h"
40 #include "utils.h"
41
42 static gint session_connect_cb          (SockInfo       *sock,
43                                          gpointer        data);
44 static gint session_close               (Session        *session);
45
46 static gboolean session_timeout_cb      (gpointer        data);
47
48 static gboolean session_recv_msg_idle_cb        (gpointer        data);
49 static gboolean session_recv_data_idle_cb       (gpointer        data);
50
51 static gboolean session_read_msg_cb     (SockInfo       *source,
52                                          GIOCondition    condition,
53                                          gpointer        data);
54 static gboolean session_read_data_cb    (SockInfo       *source,
55                                          GIOCondition    condition,
56                                          gpointer        data);
57 static gboolean session_write_msg_cb    (SockInfo       *source,
58                                          GIOCondition    condition,
59                                          gpointer        data);
60 static gboolean session_write_data_cb   (SockInfo       *source,
61                                          GIOCondition    condition,
62                                          gpointer        data);
63
64
65 void session_init(Session *session)
66 {
67         session->type = SESSION_UNKNOWN;
68         session->sock = NULL;
69         session->server = NULL;
70         session->port = 0;
71 #if USE_OPENSSL
72         session->ssl_type = SSL_NONE;
73 #endif
74         session->nonblocking = TRUE;
75         session->state = SESSION_READY;
76         session->last_access_time = time(NULL);
77
78         gettimeofday(&session->tv_prev, NULL);
79
80         session->conn_id = 0;
81
82         session->io_tag = 0;
83
84         session->read_buf_p = session->read_buf;
85         session->read_buf_len = 0;
86
87         session->read_msg_buf = g_string_sized_new(1024);
88         session->read_data_buf = g_byte_array_new();
89
90         session->write_buf = NULL;
91         session->write_buf_p = NULL;
92         session->write_buf_len = 0;
93
94         session->timeout_tag = 0;
95         session->timeout_interval = 0;
96
97         session->data = NULL;
98 }
99
100 /*!
101  *\brief        Set up parent and child process
102  *              Childloop: Read commands from parent,
103  *              send to server, get answer, pass to parent
104  *
105  *\param        session Contains session information
106  *              server to connect to
107  *              port to connect to
108  *
109  *\return        0 : success
110  *              -1 : pipe / fork errors (parent)
111  *               1 : connection error (child)
112  */
113 gint session_connect(Session *session, const gchar *server, gushort port)
114 {
115         session->server = g_strdup(server);
116         session->port = port;
117
118         session->conn_id = sock_connect_async(server, port, session_connect_cb,
119                                               session);
120         if (session->conn_id < 0) {
121                 g_warning("can't connect to server.");
122                 session_close(session);
123                 return -1;
124         }
125
126         return 0;
127 }
128
129 static gint session_connect_cb(SockInfo *sock, gpointer data)
130 {
131         Session *session = SESSION(data);
132
133         session->conn_id = 0;
134
135         if (!sock) {
136                 g_warning("can't connect to server.");
137                 session->state = SESSION_ERROR;
138                 return -1;
139         }
140
141         session->sock = sock;
142
143 #if USE_OPENSSL
144         if (session->ssl_type == SSL_TUNNEL) {
145                 sock_set_nonblocking_mode(sock, FALSE);
146                 if (!ssl_init_socket(sock)) {
147                         g_warning("can't initialize SSL.");
148                         session->state = SESSION_ERROR;
149                         return -1;
150                 }
151         }
152 #endif
153
154         sock_set_nonblocking_mode(sock, session->nonblocking);
155
156         debug_print("session (%p): connected\n", session);
157
158         session->state = SESSION_RECV;
159         session->io_tag = sock_add_watch(session->sock, G_IO_IN,
160                                          session_read_msg_cb,
161                                          session);
162
163         return 0;
164 }
165
166 /*!
167  *\brief        child and parent: send DISCONNECT message to other process
168  *
169  *\param        session Contains session information
170  *
171  *\return        0 : success
172  */
173 gint session_disconnect(Session *session)
174 {
175         session_close(session);
176         return 0;
177 }
178
179 /*!
180  *\brief        parent ?
181  *
182  *\param        session Contains session information
183  */
184 void session_destroy(Session *session)
185 {
186         g_return_if_fail(session != NULL);
187         g_return_if_fail(session->destroy != NULL);
188
189         session_close(session);
190         session->destroy(session);
191         g_free(session->server);
192         g_string_free(session->read_msg_buf, TRUE);
193         g_byte_array_free(session->read_data_buf, TRUE);
194         g_free(session->read_data_terminator);
195         g_free(session->write_buf);
196
197         debug_print("session (%p): destroyed\n", session);
198
199         g_free(session);
200 }
201
202 gboolean session_is_connected(Session *session)
203 {
204         return (session->state == SESSION_READY ||
205                 session->state == SESSION_SEND ||
206                 session->state == SESSION_RECV);
207 }
208
209 void session_set_access_time(Session *session)
210 {
211         session->last_access_time = time(NULL);
212 }
213
214 void session_set_timeout(Session *session, guint interval)
215 {
216         if (session->timeout_tag > 0)
217                 g_source_remove(session->timeout_tag);
218
219         session->timeout_interval = interval;
220         if (interval > 0)
221                 session->timeout_tag =
222                         g_timeout_add(interval, session_timeout_cb, session);
223         else
224                 session->timeout_tag = 0;
225 }
226
227 static gboolean session_timeout_cb(gpointer data)
228 {
229         Session *session = SESSION(data);
230
231         g_warning("session timeout.\n");
232
233         if (session->io_tag > 0) {
234                 g_source_remove(session->io_tag);
235                 session->io_tag = 0;
236         }
237
238         session->timeout_tag = 0;
239         session->state = SESSION_TIMEOUT;
240
241         return FALSE;
242 }
243
244 void session_set_recv_message_notify(Session *session,
245                                      RecvMsgNotify notify_func, gpointer data)
246 {
247         session->recv_msg_notify = notify_func;
248         session->recv_msg_notify_data = data;
249 }
250
251 void session_set_recv_data_progressive_notify
252                                         (Session *session,
253                                          RecvDataProgressiveNotify notify_func,
254                                          gpointer data)
255 {
256         session->recv_data_progressive_notify = notify_func,
257         session->recv_data_progressive_notify_data = data;
258 }
259
260 void session_set_recv_data_notify(Session *session, RecvDataNotify notify_func,
261                                   gpointer data)
262 {
263         session->recv_data_notify = notify_func;
264         session->recv_data_notify_data = data;
265 }
266
267 void session_set_send_data_progressive_notify
268                                         (Session *session,
269                                          SendDataProgressiveNotify notify_func,
270                                          gpointer data)
271 {
272         session->send_data_progressive_notify = notify_func;
273         session->send_data_progressive_notify_data = data;
274 }
275
276 void session_set_send_data_notify(Session *session, SendDataNotify notify_func,
277                                   gpointer data)
278 {
279         session->send_data_notify = notify_func;
280         session->send_data_notify_data = data;
281 }
282
283 /*!
284  *\brief        child and parent cleanup (child closes first)
285  *
286  *\param        session Contains session information
287  *
288  *\return        0 : success
289  */
290 static gint session_close(Session *session)
291 {
292         g_return_val_if_fail(session != NULL, -1);
293
294         if (session->conn_id > 0) {
295                 sock_connect_async_cancel(session->conn_id);
296                 session->conn_id = 0;
297                 debug_print("session (%p): connection cancelled\n", session);
298         }
299
300         session_set_timeout(session, 0);
301
302         if (session->io_tag > 0) {
303                 g_source_remove(session->io_tag);
304                 session->io_tag = 0;
305         }
306
307         if (session->sock) {
308                 sock_close(session->sock);
309                 session->sock = NULL;
310                 session->state = SESSION_DISCONNECTED;
311                 debug_print("session (%p): closed\n", session);
312         }
313
314         return 0;
315 }
316
317 #if USE_OPENSSL
318 gint session_start_tls(Session *session)
319 {
320         gboolean nb_mode;
321
322         nb_mode = sock_is_nonblocking_mode(session->sock);
323
324         if (nb_mode)
325                 sock_set_nonblocking_mode(session->sock, FALSE);
326
327         if (!ssl_init_socket_with_method(session->sock, SSL_METHOD_TLSv1)) {
328                 g_warning("can't start TLS session.\n");
329                 if (nb_mode)
330                         sock_set_nonblocking_mode(session->sock, TRUE);
331                 return -1;
332         }
333
334         if (nb_mode)
335                 sock_set_nonblocking_mode(session->sock, session->nonblocking);
336
337         return 0;
338 }
339 #endif
340
341 gint session_send_msg(Session *session, SessionMsgType type, const gchar *msg)
342 {
343         gboolean ret;
344
345         g_return_val_if_fail(session->write_buf == NULL, -1);
346         g_return_val_if_fail(msg != NULL, -1);
347         g_return_val_if_fail(msg[0] != '\0', -1);
348
349         session->state = SESSION_SEND;
350         session->write_buf = g_strconcat(msg, "\r\n", NULL);
351         session->write_buf_p = session->write_buf;
352         session->write_buf_len = strlen(msg) + 2;
353
354         ret = session_write_msg_cb(session->sock, G_IO_OUT, session);
355
356         if (ret == TRUE)
357                 session->io_tag = sock_add_watch(session->sock, G_IO_OUT,
358                                                  session_write_msg_cb, session);
359         else if (session->state == SESSION_ERROR)
360                 return -1;
361
362         return 0;
363 }
364
365 gint session_recv_msg(Session *session)
366 {
367         g_return_val_if_fail(session->read_msg_buf->len == 0, -1);
368
369         session->state = SESSION_RECV;
370
371         if (session->read_buf_len > 0)
372                 g_idle_add(session_recv_msg_idle_cb, session);
373         else
374                 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
375                                                  session_read_msg_cb, session);
376
377         return 0;
378 }
379
380 static gboolean session_recv_msg_idle_cb(gpointer data)
381 {
382         Session *session = SESSION(data);
383         gboolean ret;
384
385         ret = session_read_msg_cb(session->sock, G_IO_IN, session);
386
387         if (ret == TRUE)
388                 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
389                                                  session_read_msg_cb, session);
390
391         return FALSE;
392 }
393
394 /*!
395  *\brief        parent (child?): send data to other process
396  *
397  *\param        session Contains session information
398  *              data Data to send
399  *              size Bytes to send
400  *
401  *\return        0 : success
402  *              -1 : error
403  */
404 gint session_send_data(Session *session, const guchar *data, guint size)
405 {
406         gboolean ret;
407
408         g_return_val_if_fail(session->write_buf == NULL, -1);
409         g_return_val_if_fail(data != NULL, -1);
410         g_return_val_if_fail(size != 0, -1);
411
412         session->state = SESSION_SEND;
413
414         session->write_buf = g_malloc(size);
415         session->write_buf_p = session->write_buf;
416         memcpy(session->write_buf, data, size);
417         session->write_buf_len = size;
418         gettimeofday(&session->tv_prev, NULL);
419
420         ret = session_write_data_cb(session->sock, G_IO_OUT, session);
421
422         if (ret == TRUE)
423                 session->io_tag = sock_add_watch(session->sock, G_IO_OUT,
424                                                  session_write_data_cb,
425                                                  session);
426         else if (session->state == SESSION_ERROR)
427                 return -1;
428
429         return 0;
430 }
431
432 gint session_recv_data(Session *session, guint size, const gchar *terminator)
433 {
434         g_return_val_if_fail(session->read_data_buf->len == 0, -1);
435
436         session->state = SESSION_RECV;
437
438         g_free(session->read_data_terminator);
439         session->read_data_terminator = g_strdup(terminator);
440         gettimeofday(&session->tv_prev, NULL);
441
442         if (session->read_buf_len > 0)
443                 g_idle_add(session_recv_data_idle_cb, session);
444         else
445                 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
446                                                  session_read_data_cb, session);
447
448         return 0;
449 }
450
451 static gboolean session_recv_data_idle_cb(gpointer data)
452 {
453         Session *session = SESSION(data);
454         gboolean ret;
455
456         ret = session_read_data_cb(session->sock, G_IO_IN, session);
457
458         if (ret == TRUE)
459                 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
460                                                  session_read_data_cb, session);
461
462         return FALSE;
463 }
464
465 static gboolean session_read_msg_cb(SockInfo *source, GIOCondition condition,
466                                     gpointer data)
467 {
468         Session *session = SESSION(data);
469         gchar buf[SESSION_BUFFSIZE];
470         gint line_len;
471         gchar *newline;
472         gchar *msg;
473         gint ret;
474
475         g_return_val_if_fail(condition == G_IO_IN, FALSE);
476
477         session_set_timeout(session, session->timeout_interval);
478
479         if (session->read_buf_len == 0) {
480                 gint read_len;
481
482                 read_len = sock_read(session->sock, session->read_buf,
483                                      SESSION_BUFFSIZE - 1);
484
485                 if (read_len == -1 && session->state == SESSION_DISCONNECTED) {
486                         g_warning ("sock_read: session disconnected\n");
487                         if (session->io_tag > 0) {
488                                 g_source_remove(session->io_tag);
489                                 session->io_tag = 0;
490                         }
491                         return FALSE;
492                 }
493                 
494                 if (read_len == 0) {
495                         g_warning("sock_read: received EOF\n");
496                         session->state = SESSION_EOF;
497                         return FALSE;
498                 }
499
500                 if (read_len < 0) {
501                         switch (errno) {
502                         case EAGAIN:
503                                 return TRUE;
504                         default:
505                                 g_warning("sock_read: %s\n", g_strerror(errno));
506                                 session->state = SESSION_ERROR;
507                                 return FALSE;
508                         }
509                 }
510
511                 session->read_buf_len = read_len;
512         }
513
514         if ((newline = memchr(session->read_buf_p, '\n', session->read_buf_len))
515                 != NULL)
516                 line_len = newline - session->read_buf_p + 1;
517         else
518                 line_len = session->read_buf_len;
519
520         if (line_len == 0)
521                 return TRUE;
522
523         memcpy(buf, session->read_buf_p, line_len);
524         buf[line_len] = '\0';
525
526         g_string_append(session->read_msg_buf, buf);
527
528         session->read_buf_len -= line_len;
529         if (session->read_buf_len == 0)
530                 session->read_buf_p = session->read_buf;
531         else
532                 session->read_buf_p += line_len;
533
534         /* incomplete read */
535         if (buf[line_len - 1] != '\n')
536                 return TRUE;
537
538         /* complete */
539         if (session->io_tag > 0) {
540                 g_source_remove(session->io_tag);
541                 session->io_tag = 0;
542         }
543
544         /* callback */
545         msg = g_strdup(session->read_msg_buf->str);
546         strretchomp(msg);
547         g_string_truncate(session->read_msg_buf, 0);
548
549         ret = session->recv_msg(session, msg);
550         session->recv_msg_notify(session, msg, session->recv_msg_notify_data);
551
552         g_free(msg);
553
554         if (ret < 0)
555                 session->state = SESSION_ERROR;
556
557         return FALSE;
558 }
559
560 static gboolean session_read_data_cb(SockInfo *source, GIOCondition condition,
561                                      gpointer data)
562 {
563         Session *session = SESSION(data);
564         GByteArray *data_buf;
565         gint terminator_len;
566         gboolean complete = FALSE;
567         guint data_len;
568         gint ret;
569
570         g_return_val_if_fail(condition == G_IO_IN, FALSE);
571
572         session_set_timeout(session, session->timeout_interval);
573
574         if (session->read_buf_len == 0) {
575                 gint read_len;
576
577                 read_len = sock_read(session->sock, session->read_buf,
578                                      SESSION_BUFFSIZE);
579
580                 if (read_len == 0) {
581                         g_warning("sock_read: received EOF\n");
582                         session->state = SESSION_EOF;
583                         return FALSE;
584                 }
585
586                 if (read_len < 0) {
587                         switch (errno) {
588                         case EAGAIN:
589                                 return TRUE;
590                         default:
591                                 g_warning("sock_read: %s\n", g_strerror(errno));
592                                 session->state = SESSION_ERROR;
593                                 return FALSE;
594                         }
595                 }
596
597                 session->read_buf_len = read_len;
598         }
599
600         data_buf = session->read_data_buf;
601         terminator_len = strlen(session->read_data_terminator);
602
603         if (session->read_buf_len == 0)
604                 return TRUE;
605
606         g_byte_array_append(data_buf, session->read_buf_p,
607                             session->read_buf_len);
608
609         session->read_buf_len = 0;
610         session->read_buf_p = session->read_buf;
611
612         /* check if data is terminated */
613         if (data_buf->len >= terminator_len) {
614                 if (memcmp(data_buf->data, session->read_data_terminator,
615                            terminator_len) == 0)
616                         complete = TRUE;
617                 else if (data_buf->len >= terminator_len + 2 &&
618                          memcmp(data_buf->data + data_buf->len -
619                                 (terminator_len + 2), "\r\n", 2) == 0 &&
620                          memcmp(data_buf->data + data_buf->len -
621                                 terminator_len, session->read_data_terminator,
622                                 terminator_len) == 0)
623                         complete = TRUE;
624         }
625
626         /* incomplete read */
627         if (!complete) {
628                 struct timeval tv_cur;
629
630                 gettimeofday(&tv_cur, NULL);
631                 if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 ||
632                     tv_cur.tv_usec - session->tv_prev.tv_usec >
633                     UI_REFRESH_INTERVAL) {
634                         session->recv_data_progressive_notify
635                                 (session, data_buf->len, 0,
636                                  session->recv_data_progressive_notify_data);
637                         gettimeofday(&session->tv_prev, NULL);
638                 }
639                 return TRUE;
640         }
641
642         /* complete */
643         if (session->io_tag > 0) {
644                 g_source_remove(session->io_tag);
645                 session->io_tag = 0;
646         }
647
648         data_len = data_buf->len - terminator_len;
649
650         /* callback */
651         ret = session->recv_data_finished(session, (gchar *)data_buf->data,
652                                           data_len);
653
654         g_byte_array_set_size(data_buf, 0);
655
656         session->recv_data_notify(session, data_len,
657                                   session->recv_data_notify_data);
658
659         if (ret < 0)
660                 session->state = SESSION_ERROR;
661
662         return FALSE;
663 }
664
665 static gint session_write_buf(Session *session)
666 {
667         gint write_len;
668         gint to_write_len;
669
670         g_return_val_if_fail(session->write_buf != NULL, -1);
671         g_return_val_if_fail(session->write_buf_p != NULL, -1);
672         g_return_val_if_fail(session->write_buf_len > 0, -1);
673
674         to_write_len = session->write_buf_len -
675                 (session->write_buf_p - session->write_buf);
676         to_write_len = MIN(to_write_len, SESSION_BUFFSIZE);
677
678         write_len = sock_write(session->sock, session->write_buf_p,
679                                to_write_len);
680
681         if (write_len < 0) {
682                 switch (errno) {
683                 case EAGAIN:
684                         write_len = 0;
685                         break;
686                 default:
687                         g_warning("sock_write: %s\n", g_strerror(errno));
688                         session->state = SESSION_ERROR;
689                         return -1;
690                 }
691         }
692
693         /* incomplete write */
694         if (session->write_buf_p - session->write_buf + write_len <
695             session->write_buf_len) {
696                 session->write_buf_p += write_len;
697                 return 1;
698         }
699
700         g_free(session->write_buf);
701         session->write_buf = NULL;
702         session->write_buf_p = NULL;
703         session->write_buf_len = 0;
704
705         return 0;
706 }
707
708 static gboolean session_write_msg_cb(SockInfo *source, GIOCondition condition,
709                                      gpointer data)
710 {
711         Session *session = SESSION(data);
712         gint ret;
713
714         g_return_val_if_fail(condition == G_IO_OUT, FALSE);
715         g_return_val_if_fail(session->write_buf != NULL, FALSE);
716         g_return_val_if_fail(session->write_buf_p != NULL, FALSE);
717         g_return_val_if_fail(session->write_buf_len > 0, FALSE);
718
719         ret = session_write_buf(session);
720
721         if (ret < 0) {
722                 session->state = SESSION_ERROR;
723                 return FALSE;
724         } else if (ret > 0)
725                 return TRUE;
726
727         if (session->io_tag > 0) {
728                 g_source_remove(session->io_tag);
729                 session->io_tag = 0;
730         }
731
732         session_recv_msg(session);
733
734         return FALSE;
735 }
736
737 static gboolean session_write_data_cb(SockInfo *source,
738                                       GIOCondition condition, gpointer data)
739 {
740         Session *session = SESSION(data);
741         guint write_buf_len;
742         gint ret;
743
744         g_return_val_if_fail(condition == G_IO_OUT, FALSE);
745         g_return_val_if_fail(session->write_buf != NULL, FALSE);
746         g_return_val_if_fail(session->write_buf_p != NULL, FALSE);
747         g_return_val_if_fail(session->write_buf_len > 0, FALSE);
748
749         write_buf_len = session->write_buf_len;
750
751         ret = session_write_buf(session);
752
753         if (ret < 0) {
754                 session->state = SESSION_ERROR;
755                 return FALSE;
756         } else if (ret > 0) {
757                 struct timeval tv_cur;
758
759                 gettimeofday(&tv_cur, NULL);
760                 if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 ||
761                     tv_cur.tv_usec - session->tv_prev.tv_usec >
762                     UI_REFRESH_INTERVAL) {
763                         session_set_timeout(session, session->timeout_interval);
764                         session->send_data_progressive_notify
765                                 (session,
766                                  session->write_buf_p - session->write_buf,
767                                  write_buf_len,
768                                  session->send_data_progressive_notify_data);
769                         gettimeofday(&session->tv_prev, NULL);
770                 }
771                 return TRUE;
772         }
773
774         if (session->io_tag > 0) {
775                 g_source_remove(session->io_tag);
776                 session->io_tag = 0;
777         }
778
779         /* callback */
780         ret = session->send_data_finished(session, write_buf_len);
781         session->send_data_notify(session, write_buf_len,
782                                   session->send_data_notify_data);
783
784         return FALSE;
785 }