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