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