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