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