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