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