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