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