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