6c341568a801cbceef256d436b0ecd5f426355c1
[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 == 0) {
441                         g_warning("sock_read: received EOF\n");
442                         session->state = SESSION_EOF;
443                         return FALSE;
444                 }
445
446                 if (read_len < 0) {
447                         switch (errno) {
448                         case EAGAIN:
449                                 return TRUE;
450                         default:
451                                 g_warning("sock_read: %s\n", g_strerror(errno));
452                                 session->state = SESSION_ERROR;
453                                 return FALSE;
454                         }
455                 }
456
457                 session->read_buf_len = read_len;
458         }
459
460         if ((newline = memchr(session->read_buf_p, '\n', session->read_buf_len))
461                 != NULL)
462                 line_len = newline - session->read_buf_p + 1;
463         else
464                 line_len = session->read_buf_len;
465
466         if (line_len == 0)
467                 return TRUE;
468
469         memcpy(buf, session->read_buf_p, line_len);
470         buf[line_len] = '\0';
471
472         g_string_append(session->read_msg_buf, buf);
473
474         session->read_buf_len -= line_len;
475         if (session->read_buf_len == 0)
476                 session->read_buf_p = session->read_buf;
477         else
478                 session->read_buf_p += line_len;
479
480         /* incomplete read */
481         if (buf[line_len - 1] != '\n')
482                 return TRUE;
483
484         /* complete */
485         if (session->io_tag > 0) {
486                 g_source_remove(session->io_tag);
487                 session->io_tag = 0;
488         }
489
490         /* callback */
491         msg = g_strdup(session->read_msg_buf->str);
492         strretchomp(msg);
493         g_string_truncate(session->read_msg_buf, 0);
494
495         ret = session->recv_msg(session, msg);
496         session->recv_msg_notify(session, msg, session->recv_msg_notify_data);
497
498         g_free(msg);
499
500         if (ret < 0)
501                 session->state = SESSION_ERROR;
502
503         return FALSE;
504 }
505
506 static gboolean session_read_data_cb(SockInfo *source, GIOCondition condition,
507                                      gpointer data)
508 {
509         Session *session = SESSION(data);
510         GByteArray *data_buf;
511         gint terminator_len;
512         gboolean complete = FALSE;
513         guint data_len;
514         gint ret;
515
516         g_return_val_if_fail(condition == G_IO_IN, FALSE);
517
518         if (session->read_buf_len == 0) {
519                 gint read_len;
520
521                 read_len = sock_read(session->sock, session->read_buf,
522                                      SESSION_BUFFSIZE);
523
524                 if (read_len == 0) {
525                         g_warning("sock_read: received EOF\n");
526                         session->state = SESSION_EOF;
527                         return FALSE;
528                 }
529
530                 if (read_len < 0) {
531                         switch (errno) {
532                         case EAGAIN:
533                                 return TRUE;
534                         default:
535                                 g_warning("sock_read: %s\n", g_strerror(errno));
536                                 session->state = SESSION_ERROR;
537                                 return FALSE;
538                         }
539                 }
540
541                 session->read_buf_len = read_len;
542         }
543
544         data_buf = session->read_data_buf;
545         terminator_len = strlen(session->read_data_terminator);
546
547         if (session->read_buf_len == 0)
548                 return TRUE;
549
550         g_byte_array_append(data_buf, session->read_buf_p,
551                             session->read_buf_len);
552
553         session->read_buf_len = 0;
554         session->read_buf_p = session->read_buf;
555
556         /* check if data is terminated */
557         if (data_buf->len >= terminator_len) {
558                 if (memcmp(data_buf->data, session->read_data_terminator,
559                            terminator_len) == 0)
560                         complete = TRUE;
561                 else if (data_buf->len >= terminator_len + 2 &&
562                          memcmp(data_buf->data + data_buf->len -
563                                 (terminator_len + 2), "\r\n", 2) == 0 &&
564                          memcmp(data_buf->data + data_buf->len -
565                                 terminator_len, session->read_data_terminator,
566                                 terminator_len) == 0)
567                         complete = TRUE;
568         }
569
570         /* incomplete read */
571         if (!complete) {
572                 struct timeval tv_cur;
573
574                 gettimeofday(&tv_cur, NULL);
575                 if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 ||
576                     tv_cur.tv_usec - session->tv_prev.tv_usec >
577                     UI_REFRESH_INTERVAL) {
578                         session->recv_data_progressive_notify
579                                 (session, data_buf->len, 0,
580                                  session->recv_data_progressive_notify_data);
581                         gettimeofday(&session->tv_prev, NULL);
582                 }
583                 return TRUE;
584         }
585
586         /* complete */
587         if (session->io_tag > 0) {
588                 g_source_remove(session->io_tag);
589                 session->io_tag = 0;
590         }
591
592         data_len = data_buf->len - terminator_len;
593
594         /* callback */
595         ret = session->recv_data_finished(session, (gchar *)data_buf->data,
596                                           data_len);
597
598         g_byte_array_set_size(data_buf, 0);
599
600         session->recv_data_notify(session, data_len,
601                                   session->recv_data_notify_data);
602
603         if (ret < 0)
604                 session->state = SESSION_ERROR;
605
606         return FALSE;
607 }
608
609 static gint session_write_buf(Session *session)
610 {
611         gint write_len;
612         gint to_write_len;
613
614         g_return_val_if_fail(session->write_buf != NULL, -1);
615         g_return_val_if_fail(session->write_buf_p != NULL, -1);
616         g_return_val_if_fail(session->write_buf_len > 0, -1);
617
618         to_write_len = session->write_buf_len -
619                 (session->write_buf_p - session->write_buf);
620         to_write_len = MIN(to_write_len, SESSION_BUFFSIZE);
621
622         write_len = sock_write(session->sock, session->write_buf_p,
623                                to_write_len);
624
625         if (write_len < 0) {
626                 switch (errno) {
627                 case EAGAIN:
628                         write_len = 0;
629                         break;
630                 default:
631                         g_warning("sock_write: %s\n", g_strerror(errno));
632                         session->state = SESSION_ERROR;
633                         return -1;
634                 }
635         }
636
637         /* incomplete write */
638         if (session->write_buf_p - session->write_buf + write_len <
639             session->write_buf_len) {
640                 session->write_buf_p += write_len;
641                 return 1;
642         }
643
644         g_free(session->write_buf);
645         session->write_buf = NULL;
646         session->write_buf_p = NULL;
647         session->write_buf_len = 0;
648
649         return 0;
650 }
651
652 static gboolean session_write_msg_cb(SockInfo *source, GIOCondition condition,
653                                      gpointer data)
654 {
655         Session *session = SESSION(data);
656         gint ret;
657
658         g_return_val_if_fail(condition == G_IO_OUT, FALSE);
659         g_return_val_if_fail(session->write_buf != NULL, FALSE);
660         g_return_val_if_fail(session->write_buf_p != NULL, FALSE);
661         g_return_val_if_fail(session->write_buf_len > 0, FALSE);
662
663         ret = session_write_buf(session);
664
665         if (ret < 0) {
666                 session->state = SESSION_ERROR;
667                 return FALSE;
668         } else if (ret > 0)
669                 return TRUE;
670
671         if (session->io_tag > 0) {
672                 g_source_remove(session->io_tag);
673                 session->io_tag = 0;
674         }
675
676         session_recv_msg(session);
677
678         return FALSE;
679 }
680
681 static gboolean session_write_data_cb(SockInfo *source,
682                                       GIOCondition condition, gpointer data)
683 {
684         Session *session = SESSION(data);
685         guint write_buf_len;
686         gint ret;
687
688         g_return_val_if_fail(condition == G_IO_OUT, FALSE);
689         g_return_val_if_fail(session->write_buf != NULL, FALSE);
690         g_return_val_if_fail(session->write_buf_p != NULL, FALSE);
691         g_return_val_if_fail(session->write_buf_len > 0, FALSE);
692
693         write_buf_len = session->write_buf_len;
694
695         ret = session_write_buf(session);
696
697         if (ret < 0) {
698                 session->state = SESSION_ERROR;
699                 return FALSE;
700         } else if (ret > 0) {
701                 struct timeval tv_cur;
702
703                 gettimeofday(&tv_cur, NULL);
704                 if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 ||
705                     tv_cur.tv_usec - session->tv_prev.tv_usec >
706                     UI_REFRESH_INTERVAL) {
707                         session->send_data_progressive_notify
708                                 (session,
709                                  session->write_buf_p - session->write_buf,
710                                  write_buf_len,
711                                  session->send_data_progressive_notify_data);
712                         gettimeofday(&session->tv_prev, NULL);
713                 }
714                 return TRUE;
715         }
716
717         if (session->io_tag > 0) {
718                 g_source_remove(session->io_tag);
719                 session->io_tag = 0;
720         }
721
722         /* callback */
723         ret = session->send_data_finished(session, write_buf_len);
724         session->send_data_notify(session, write_buf_len,
725                                   session->send_data_notify_data);
726
727         return FALSE;
728 }