* src/procmsg.c
[claws.git] / src / send.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2002 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 #include <gtk/gtkmain.h>
28 #include <gtk/gtksignal.h>
29 #include <gtk/gtkwindow.h>
30 #include <gtk/gtkclist.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <sys/time.h>
34 #include <signal.h>
35 #include <unistd.h>
36
37 #include "intl.h"
38 #include "send.h"
39 #include "socket.h"
40 #include "ssl.h"
41 #include "smtp.h"
42 #include "esmtp.h"
43 #include "prefs_common.h"
44 #include "prefs_account.h"
45 #include "account.h"
46 #include "compose.h"
47 #include "progressdialog.h"
48 #include "inputdialog.h"
49 #include "manage_window.h"
50 #include "procmsg.h"
51 #include "procheader.h"
52 #include "utils.h"
53 #include "gtkutils.h"
54
55 typedef struct _SendProgressDialog      SendProgressDialog;
56
57 struct _SendProgressDialog
58 {
59         ProgressDialog *dialog;
60         GList *queue_list;
61         gboolean cancelled;
62 };
63
64 #if USE_SSL
65 static SockInfo *send_smtp_open (const gchar *server, gushort port,
66                                  const gchar *domain, gboolean use_smtp_auth,
67                                  SSLSMTPType ssl_type);
68 #else
69 static SockInfo *send_smtp_open (const gchar *server, gushort port,
70                                  const gchar *domain, gboolean use_smtp_auth);
71 #endif
72
73 static gint send_message_data   (SendProgressDialog *dialog, SockInfo *sock,
74                                  FILE *fp, gint size);
75
76 static SendProgressDialog *send_progress_dialog_create(void);
77 static void send_progress_dialog_destroy(SendProgressDialog *dialog);
78 static void send_cancel(GtkWidget *widget, gpointer data);
79
80
81 gint send_message(const gchar *file, PrefsAccount *ac_prefs, GSList *to_list)
82 {
83         FILE *fp;
84         gint val;
85
86         g_return_val_if_fail(file != NULL, -1);
87         g_return_val_if_fail(ac_prefs != NULL, -1);
88         g_return_val_if_fail(to_list != NULL, -1);
89
90         if ((fp = fopen(file, "rb")) == NULL) {
91                 FILE_OP_ERROR(file, "fopen");
92                 return -1;
93         }
94
95         printf("account: %p\n", ac_prefs);
96
97         if (ac_prefs->use_mail_command && ac_prefs->mail_command &&
98             (*ac_prefs->mail_command)) {
99                 val = send_message_local(ac_prefs->mail_command, fp);
100                 fclose(fp);
101                 return val;
102         }
103         else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
104                 val = send_message_local(prefs_common.extsend_cmd, fp);
105                 fclose(fp);
106                 return val;
107         }
108         else {
109                 val = send_message_smtp(ac_prefs, to_list, fp);
110                 
111                 fclose(fp);
112                 return val;
113         }
114 }
115
116 enum
117 {
118         Q_SENDER     = 0,
119         Q_SMTPSERVER = 1,
120         Q_RECIPIENTS = 2,
121         Q_ACCOUNT_ID = 3
122 };
123
124 #if 0
125 gint send_message_queue(const gchar *file)
126 {
127         static HeaderEntry qentry[] = {{"S:",   NULL, FALSE},
128                                        {"SSV:", NULL, FALSE},
129                                        {"R:",   NULL, FALSE},
130                                        {"AID:", NULL, FALSE},
131                                        {NULL,   NULL, FALSE}};
132         FILE *fp;
133         gint val;
134         gchar *from = NULL;
135         gchar *server = NULL;
136         GSList *to_list = NULL;
137         gchar buf[BUFFSIZE];
138         gint hnum;
139         PrefsAccount *ac = NULL;
140
141         g_return_val_if_fail(file != NULL, -1);
142
143         if ((fp = fopen(file, "rb")) == NULL) {
144                 FILE_OP_ERROR(file, "fopen");
145                 return -1;
146         }
147
148         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
149                != -1) {
150                 gchar *p = buf + strlen(qentry[hnum].name);
151
152                 switch (hnum) {
153                 case Q_SENDER:
154                         if (!from) from = g_strdup(p);
155                         break;
156                 case Q_SMTPSERVER:
157                         if (!server) server = g_strdup(p);
158                         break;
159                 case Q_RECIPIENTS:
160                         to_list = address_list_append(to_list, p);
161                         break;
162                 case Q_ACCOUNT_ID:
163                         ac = account_find_from_id(atoi(p));
164                         break;
165                 default:
166                         break;
167                 }
168         }
169
170         if (!to_list || !from) {
171                 g_warning(_("Queued message header is broken.\n"));
172                 val = -1;
173         } else if (ac && ac->use_mail_command && ac->mail_command &&
174                    (*ac->mail_command)) {
175                 val = send_message_local(ac->mail_command, fp);
176         } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
177                 val = send_message_local(prefs_common.extsend_cmd, fp);
178         } else {
179                 if (!ac) {
180                         ac = account_find_from_smtp_server(from, server);
181                         if (!ac) {
182                                 g_warning(_("Account not found. "
183                                             "Using current account...\n"));
184                                 ac = cur_account;
185                         }
186                 }
187
188                 if (ac)
189                         val = send_message_smtp(ac, to_list, fp);
190                 else {
191                         PrefsAccount tmp_ac;
192
193                         g_warning(_("Account not found.\n"));
194
195                         memset(&tmp_ac, 0, sizeof(PrefsAccount));
196                         tmp_ac.address = from;
197                         tmp_ac.smtp_server = server;
198                         tmp_ac.smtpport = SMTP_PORT;
199                         val = send_message_smtp(&tmp_ac, to_list, fp);
200                 }
201         }
202
203         slist_free_strings(to_list);
204         g_slist_free(to_list);
205         g_free(from);
206         g_free(server);
207         fclose(fp);
208
209         return val;
210 }
211 #endif
212
213 gint send_message_local(const gchar *command, FILE *fp)
214 {
215         FILE *pipefp;
216         gchar buf[BUFFSIZE];
217         int r;
218         sigset_t osig, mask;
219
220         g_return_val_if_fail(command != NULL, -1);
221         g_return_val_if_fail(fp != NULL, -1);
222
223         pipefp = popen(command, "w");
224         if (!pipefp) {
225                 g_warning(_("Can't execute external command: %s\n"), command);
226                 return -1;
227         }
228
229         while (fgets(buf, sizeof(buf), fp) != NULL) {
230                 strretchomp(buf);
231                 /* escape when a dot appears on the top */
232                 if (buf[0] == '.')
233                         fputc('.', pipefp);
234                 fputs(buf, pipefp);
235                 fputc('\n', pipefp);
236         }
237
238         /* we need to block SIGCHLD, otherwise pspell's handler will wait()
239          * the pipecommand away and pclose will return -1 because of its
240          * failed wait4().
241          */
242         sigemptyset(&mask);
243         sigaddset(&mask, SIGCHLD);
244         sigprocmask(SIG_BLOCK, &mask, &osig);
245         
246         r = pclose(pipefp);
247
248         sigprocmask(SIG_SETMASK, &osig, NULL);
249         if (r != 0) {
250                 g_warning(_("external command `%s' failed with code `%i'\n"), command, r);
251                 return -1;
252         }
253
254         return 0;
255 }
256
257 #define EXIT_IF_CANCELLED() \
258 { \
259         if (dialog->cancelled) { \
260                 sock_close(smtp_sock); \
261                 send_progress_dialog_destroy(dialog); \
262                 return -1; \
263         } \
264 }
265
266 #define SEND_EXIT_IF_ERROR(f, s) \
267 { \
268         EXIT_IF_CANCELLED(); \
269         if (!(f)) { \
270                 log_warning("Error occurred while %s\n", s); \
271                 sock_close(smtp_sock); \
272                 send_progress_dialog_destroy(dialog); \
273                 return -1; \
274         } \
275 }
276
277 #define SEND_EXIT_IF_NOTOK(f, s) \
278 { \
279         gint ok; \
280  \
281         EXIT_IF_CANCELLED(); \
282         if ((ok = (f)) != SM_OK) { \
283                 log_warning("Error occurred while %s\n", s); \
284                 if (ok == SM_AUTHFAIL) { \
285                         log_warning("SMTP AUTH failed\n"); \
286                         if (ac_prefs->tmp_pass) { \
287                                 g_free(ac_prefs->tmp_pass); \
288                                 ac_prefs->tmp_pass = NULL; \
289                         } \
290                         if (ac_prefs->tmp_smtp_pass) { \
291                                 g_free(ac_prefs->tmp_smtp_pass); \
292                                 ac_prefs->tmp_smtp_pass = NULL; \
293                         } \
294                 } \
295                 if (smtp_quit(smtp_sock) != SM_OK) \
296                         log_warning("Error occurred while sending QUIT\n"); \
297                 sock_close(smtp_sock); \
298                 send_progress_dialog_destroy(dialog); \
299                 return -1; \
300         } \
301 }
302
303 gint send_message_smtp(PrefsAccount *ac_prefs, GSList *to_list,
304                               FILE *fp)
305 {
306         SockInfo *smtp_sock = NULL;
307         SendProgressDialog *dialog;
308         GtkCList *clist;
309         const gchar *text[3];
310         gchar buf[BUFFSIZE];
311         gushort port;
312         gchar *domain;
313         gchar *user = NULL;
314         gchar *pass = NULL;
315         GSList *cur;
316         gint size;
317
318         g_return_val_if_fail(ac_prefs != NULL, -1);
319         g_return_val_if_fail(ac_prefs->address != NULL, -1);
320         g_return_val_if_fail(ac_prefs->smtp_server != NULL, -1);
321         g_return_val_if_fail(to_list != NULL, -1);
322         g_return_val_if_fail(fp != NULL, -1);
323
324         size = get_left_file_size(fp);
325         if (size < 0) return -1;
326
327 #if USE_SSL
328         port = ac_prefs->set_smtpport ? ac_prefs->smtpport :
329                 ac_prefs->ssl_smtp == SSL_SMTP_TUNNEL ? SSMTP_PORT : SMTP_PORT;
330 #else
331         port = ac_prefs->set_smtpport ? ac_prefs->smtpport : SMTP_PORT;
332 #endif
333         domain = ac_prefs->set_domain ? ac_prefs->domain : NULL;
334
335         if (ac_prefs->use_smtp_auth) {
336                 if (ac_prefs->smtp_userid) {
337                         user = ac_prefs->smtp_userid;
338                         if (ac_prefs->smtp_passwd)
339                                 pass = ac_prefs->smtp_passwd;
340                         else if (ac_prefs->tmp_smtp_pass)
341                                 pass = ac_prefs->tmp_smtp_pass;
342                         else {
343                                 pass = input_dialog_query_password
344                                         (ac_prefs->smtp_server, user);
345                                 if (!pass) pass = g_strdup("");
346                                 ac_prefs->tmp_smtp_pass = pass;
347                         }
348                 } else {
349                         user = ac_prefs->userid;
350                         if (ac_prefs->passwd)
351                                 pass = ac_prefs->passwd;
352                         else if (ac_prefs->tmp_pass)
353                                 pass = ac_prefs->tmp_pass;
354                         else {
355                                 pass = input_dialog_query_password
356                                         (ac_prefs->smtp_server, user);
357                                 if (!pass) pass = g_strdup("");
358                                 ac_prefs->tmp_pass = pass;
359                         }
360                 }
361         }
362
363         dialog = send_progress_dialog_create();
364
365         text[0] = NULL;
366         text[1] = ac_prefs->smtp_server;
367         text[2] = _("Standby");
368         clist = GTK_CLIST(dialog->dialog->clist);
369         gtk_clist_append(clist, (gchar **)text);
370
371         g_snprintf(buf, sizeof(buf), _("Connecting to SMTP server: %s ..."),
372                    ac_prefs->smtp_server);
373         log_message("%s\n", buf);
374         progress_dialog_set_label(dialog->dialog, buf);
375         gtk_clist_set_text(clist, 0, 2, _("Connecting"));
376         GTK_EVENTS_FLUSH();
377
378 #if USE_SSL
379         SEND_EXIT_IF_ERROR((smtp_sock = send_smtp_open
380                                 (ac_prefs->smtp_server, port, domain,
381                                  ac_prefs->use_smtp_auth, ac_prefs->ssl_smtp)),
382                            "connecting to server");
383 #else
384         SEND_EXIT_IF_ERROR((smtp_sock = send_smtp_open
385                                 (ac_prefs->smtp_server, port, domain,
386                                  ac_prefs->use_smtp_auth)),
387                            "connecting to server");
388 #endif
389
390         progress_dialog_set_label(dialog->dialog, _("Sending MAIL FROM..."));
391         gtk_clist_set_text(clist, 0, 2, _("Sending"));
392         GTK_EVENTS_FLUSH();
393
394         SEND_EXIT_IF_NOTOK
395                 (smtp_from(smtp_sock, ac_prefs->address, user, pass,
396                            ac_prefs->use_smtp_auth),
397                  "sending MAIL FROM");
398
399         progress_dialog_set_label(dialog->dialog, _("Sending RCPT TO..."));
400         GTK_EVENTS_FLUSH();
401
402         for (cur = to_list; cur != NULL; cur = cur->next)
403                 SEND_EXIT_IF_NOTOK(smtp_rcpt(smtp_sock, (gchar *)cur->data),
404                                    "sending RCPT TO");
405
406         progress_dialog_set_label(dialog->dialog, _("Sending DATA..."));
407         GTK_EVENTS_FLUSH();
408
409         SEND_EXIT_IF_NOTOK(smtp_data(smtp_sock), "sending DATA");
410
411         /* send main part */
412         SEND_EXIT_IF_ERROR(send_message_data(dialog, smtp_sock, fp, size) == 0,
413                            "sending data");
414
415         progress_dialog_set_label(dialog->dialog, _("Quitting..."));
416         GTK_EVENTS_FLUSH();
417
418         SEND_EXIT_IF_NOTOK(smtp_eom(smtp_sock), "terminating data");
419         SEND_EXIT_IF_NOTOK(smtp_quit(smtp_sock), "sending QUIT");
420
421         sock_close(smtp_sock);
422         send_progress_dialog_destroy(dialog);
423
424         return 0;
425 }
426
427 #undef EXIT_IF_CANCELLED
428 #undef SEND_EXIT_IF_ERROR
429 #undef SEND_EXIT_IF_NOTOK
430
431 #define EXIT_IF_CANCELLED() \
432 { \
433         if (dialog->cancelled) return -1; \
434 }
435
436 #define SEND_EXIT_IF_ERROR(f) \
437 { \
438         EXIT_IF_CANCELLED(); \
439         if ((f) <= 0) return -1; \
440 }
441
442 #define SEND_DIALOG_UPDATE() \
443 { \
444         gettimeofday(&tv_cur, NULL); \
445         if (tv_cur.tv_sec - tv_prev.tv_sec > 0 || \
446             tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) { \
447                 g_snprintf(str, sizeof(str), \
448                            _("Sending message (%d / %d bytes)"), \
449                            bytes, size); \
450                 progress_dialog_set_label(dialog->dialog, str); \
451                 progress_dialog_set_percentage \
452                         (dialog->dialog, (gfloat)bytes / (gfloat)size); \
453                 GTK_EVENTS_FLUSH(); \
454                 gettimeofday(&tv_prev, NULL); \
455         } \
456 }
457
458 static gint send_message_data(SendProgressDialog *dialog, SockInfo *sock,
459                               FILE *fp, gint size)
460 {
461         gchar buf[BUFFSIZE];
462         gchar str[BUFFSIZE];
463         gint bytes = 0;
464         struct timeval tv_prev, tv_cur;
465
466         gettimeofday(&tv_prev, NULL);
467
468         /* output header part */
469         while (fgets(buf, sizeof(buf), fp) != NULL) {
470                 bytes += strlen(buf);
471                 strretchomp(buf);
472
473                 SEND_DIALOG_UPDATE();
474
475                 if (!g_strncasecmp(buf, "Bcc:", 4)) {
476                         gint next;
477
478                         for (;;) {
479                                 next = fgetc(fp);
480                                 if (next == EOF)
481                                         break;
482                                 else if (next != ' ' && next != '\t') {
483                                         ungetc(next, fp);
484                                         break;
485                                 }
486                                 if (fgets(buf, sizeof(buf), fp) == NULL)
487                                         break;
488                                 else
489                                         bytes += strlen(buf);
490                         }
491                 } else {
492                         SEND_EXIT_IF_ERROR(sock_puts(sock, buf));
493                         if (buf[0] == '\0')
494                                 break;
495                 }
496         }
497
498         /* output body part */
499         while (fgets(buf, sizeof(buf), fp) != NULL) {
500                 bytes += strlen(buf);
501                 strretchomp(buf);
502
503                 SEND_DIALOG_UPDATE();
504
505                 /* escape when a dot appears on the top */
506                 if (buf[0] == '.')
507                         SEND_EXIT_IF_ERROR(sock_write(sock, ".", 1));
508
509                 SEND_EXIT_IF_ERROR(sock_puts(sock, buf));
510         }
511
512         g_snprintf(str, sizeof(str), _("Sending message (%d / %d bytes)"),
513                    bytes, size);
514         progress_dialog_set_label(dialog->dialog, str);
515         progress_dialog_set_percentage
516                 (dialog->dialog, (gfloat)bytes / (gfloat)size);
517         GTK_EVENTS_FLUSH();
518
519         return 0;
520 }
521
522 #undef EXIT_IF_CANCELLED
523 #undef SEND_EXIT_IF_ERROR
524 #undef SEND_DIALOG_UPDATE
525
526 #if USE_SSL
527 static SockInfo *send_smtp_open(const gchar *server, gushort port,
528                                 const gchar *domain, gboolean use_smtp_auth,
529                                 SSLSMTPType ssl_type)
530 #else
531 static SockInfo *send_smtp_open(const gchar *server, gushort port,
532                                 const gchar *domain, gboolean use_smtp_auth)
533 #endif
534 {
535         SockInfo *sock;
536         gint val;
537
538         g_return_val_if_fail(server != NULL, NULL);
539
540         if ((sock = sock_connect(server, port)) == NULL) {
541                 log_warning(_("Can't connect to SMTP server: %s:%d\n"),
542                             server, port);
543                 return NULL;
544         }
545
546 #if USE_SSL
547         if (ssl_type == SSL_SMTP_TUNNEL && !ssl_init_socket(sock)) {
548                 log_warning(_("SSL connection failed"));
549                 sock_close(sock);
550                 return NULL;
551         }
552 #endif
553
554         if (smtp_ok(sock) != SM_OK) {
555                 log_warning(_("Error occurred while connecting to %s:%d\n"),
556                             server, port);
557                 sock_close(sock);
558                 return NULL;
559         }
560
561 #if USE_SSL
562         val = smtp_helo(sock, domain ? domain : get_domain_name(),
563                         use_smtp_auth || ssl_type == SSL_SMTP_STARTTLS);
564 #else
565         val = smtp_helo(sock, domain ? domain : get_domain_name(),
566                         use_smtp_auth);
567 #endif
568
569         if (val != SM_OK) {
570                 log_warning(_("Error occurred while sending HELO\n"));
571                 sock_close(sock);
572                 return NULL;
573         }
574
575 #if USE_SSL
576         if (ssl_type == SSL_SMTP_STARTTLS) {
577                 val = esmtp_starttls(sock);
578                 if (val != SM_OK) {
579                         log_warning(_("Error occurred while sending STARTTLS\n"));
580                         sock_close(sock);
581                         return NULL;
582                 }
583                 if (!ssl_init_socket_with_method(sock, SSL_METHOD_TLSv1)) {
584                         sock_close(sock);
585                         return NULL;
586                 }
587                 val = esmtp_ehlo(sock, domain ? domain : get_domain_name());
588                 if (val != SM_OK) {
589                         log_warning(_("Error occurred while sending EHLO\n"));
590                         sock_close(sock);
591                         return NULL;
592                 }
593         }
594 #endif
595
596         return sock;
597 }
598
599
600 static SendProgressDialog *send_progress_dialog_create(void)
601 {
602         SendProgressDialog *dialog;
603         ProgressDialog *progress;
604
605         dialog = g_new0(SendProgressDialog, 1);
606
607         progress = progress_dialog_create();
608         gtk_window_set_title(GTK_WINDOW(progress->window),
609                              _("Sending message"));
610         gtk_signal_connect(GTK_OBJECT(progress->cancel_btn), "clicked",
611                            GTK_SIGNAL_FUNC(send_cancel), dialog);
612         gtk_signal_connect(GTK_OBJECT(progress->window), "delete_event",
613                            GTK_SIGNAL_FUNC(gtk_true), NULL);
614         gtk_window_set_modal(GTK_WINDOW(progress->window), TRUE);
615         manage_window_set_transient(GTK_WINDOW(progress->window));
616
617         progress_dialog_set_value(progress, 0.0);
618
619         gtk_widget_show_now(progress->window);
620
621         dialog->dialog = progress;
622         dialog->queue_list = NULL;
623         dialog->cancelled = FALSE;
624
625         return dialog;
626 }
627
628 static void send_progress_dialog_destroy(SendProgressDialog *dialog)
629 {
630         g_return_if_fail(dialog != NULL);
631
632         progress_dialog_destroy(dialog->dialog);
633         g_free(dialog);
634 }
635
636 static void send_cancel(GtkWidget *widget, gpointer data)
637 {
638         SendProgressDialog *dialog = data;
639
640         dialog->cancelled = TRUE;
641 }