d332ab8f3414de9adf39f6f6e68449f7f0bdab6e
[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 *pass = NULL;
280         GSList *cur;
281         gint size;
282
283         g_return_val_if_fail(ac_prefs != NULL, -1);
284         g_return_val_if_fail(ac_prefs->address != NULL, -1);
285         g_return_val_if_fail(ac_prefs->smtp_server != NULL, -1);
286         g_return_val_if_fail(to_list != NULL, -1);
287         g_return_val_if_fail(fp != NULL, -1);
288
289         size = get_left_file_size(fp);
290         if (size < 0) return -1;
291
292 #if USE_SSL
293         port = ac_prefs->set_smtpport ? ac_prefs->smtpport :
294                 ac_prefs->ssl_smtp == SSL_SMTP_TUNNEL ? SSMTP_PORT : SMTP_PORT;
295 #else
296         port = ac_prefs->set_smtpport ? ac_prefs->smtpport : SMTP_PORT;
297 #endif
298         domain = ac_prefs->set_domain ? ac_prefs->domain : NULL;
299
300         if (ac_prefs->use_smtp_auth) {
301                 if (ac_prefs->passwd)
302                         pass = ac_prefs->passwd;
303                 else if (ac_prefs->tmp_pass)
304                         pass = ac_prefs->tmp_pass;
305                 else {
306                         pass = send_query_password(ac_prefs->smtp_server,
307                                                    ac_prefs->userid);
308                         if (!pass) pass = g_strdup("");
309                         ac_prefs->tmp_pass = pass;
310                 }
311         }
312
313         dialog = send_progress_dialog_create();
314
315         text[0] = NULL;
316         text[1] = ac_prefs->smtp_server;
317         text[2] = _("Standby");
318         clist = GTK_CLIST(dialog->dialog->clist);
319         gtk_clist_append(clist, (gchar **)text);
320
321         g_snprintf(buf, sizeof(buf), _("Connecting to SMTP server: %s ..."),
322                    ac_prefs->smtp_server);
323         log_message("%s\n", buf);
324         progress_dialog_set_label(dialog->dialog, buf);
325         gtk_clist_set_text(clist, 0, 2, _("Connecting"));
326         GTK_EVENTS_FLUSH();
327
328 #if USE_SSL
329         SEND_EXIT_IF_ERROR((smtp_sock = send_smtp_open
330                                 (ac_prefs->smtp_server, port, domain,
331                                  ac_prefs->use_smtp_auth, ac_prefs->ssl_smtp)),
332                            "connecting to server");
333 #else
334         SEND_EXIT_IF_ERROR((smtp_sock = send_smtp_open
335                                 (ac_prefs->smtp_server, port, domain,
336                                  ac_prefs->use_smtp_auth)),
337                            "connecting to server");
338 #endif
339
340         progress_dialog_set_label(dialog->dialog, _("Sending MAIL FROM..."));
341         gtk_clist_set_text(clist, 0, 2, _("Sending"));
342         GTK_EVENTS_FLUSH();
343
344         SEND_EXIT_IF_NOTOK
345                 (smtp_from(smtp_sock, ac_prefs->address, ac_prefs->userid,
346                            pass, ac_prefs->use_smtp_auth),
347                  "sending MAIL FROM");
348
349         progress_dialog_set_label(dialog->dialog, _("Sending RCPT TO..."));
350         GTK_EVENTS_FLUSH();
351
352         for (cur = to_list; cur != NULL; cur = cur->next)
353                 SEND_EXIT_IF_NOTOK(smtp_rcpt(smtp_sock, (gchar *)cur->data),
354                                    "sending RCPT TO");
355
356         progress_dialog_set_label(dialog->dialog, _("Sending DATA..."));
357         GTK_EVENTS_FLUSH();
358
359         SEND_EXIT_IF_NOTOK(smtp_data(smtp_sock), "sending DATA");
360
361         /* send main part */
362         SEND_EXIT_IF_ERROR(send_message_data(dialog, smtp_sock, fp, size) == 0,
363                            "sending data");
364
365         progress_dialog_set_label(dialog->dialog, _("Quitting..."));
366         GTK_EVENTS_FLUSH();
367
368         SEND_EXIT_IF_NOTOK(smtp_eom(smtp_sock), "terminating data");
369         SEND_EXIT_IF_NOTOK(smtp_quit(smtp_sock), "sending QUIT");
370
371         sock_close(smtp_sock);
372         send_progress_dialog_destroy(dialog);
373
374         return 0;
375 }
376
377 #undef EXIT_IF_CANCELLED
378 #undef SEND_EXIT_IF_ERROR
379 #undef SEND_EXIT_IF_NOTOK
380
381 #define EXIT_IF_CANCELLED() \
382 { \
383         if (dialog->cancelled) return -1; \
384 }
385
386 #define SEND_EXIT_IF_ERROR(f) \
387 { \
388         EXIT_IF_CANCELLED(); \
389         if ((f) <= 0) return -1; \
390 }
391
392 #define SEND_DIALOG_UPDATE() \
393 { \
394         gettimeofday(&tv_cur, NULL); \
395         if (tv_cur.tv_sec - tv_prev.tv_sec > 0 || \
396             tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) { \
397                 g_snprintf(str, sizeof(str), \
398                            _("Sending message (%d / %d bytes)"), \
399                            bytes, size); \
400                 progress_dialog_set_label(dialog->dialog, str); \
401                 progress_dialog_set_percentage \
402                         (dialog->dialog, (gfloat)bytes / (gfloat)size); \
403                 GTK_EVENTS_FLUSH(); \
404                 gettimeofday(&tv_prev, NULL); \
405         } \
406 }
407
408 static gint send_message_data(SendProgressDialog *dialog, SockInfo *sock,
409                               FILE *fp, gint size)
410 {
411         gchar buf[BUFFSIZE];
412         gchar str[BUFFSIZE];
413         gint bytes = 0;
414         struct timeval tv_prev, tv_cur;
415
416         gettimeofday(&tv_prev, NULL);
417
418         /* output header part */
419         while (fgets(buf, sizeof(buf), fp) != NULL) {
420                 bytes += strlen(buf);
421                 strretchomp(buf);
422
423                 SEND_DIALOG_UPDATE();
424
425                 if (!g_strncasecmp(buf, "Bcc:", 4)) {
426                         gint next;
427
428                         for (;;) {
429                                 next = fgetc(fp);
430                                 if (next == EOF)
431                                         break;
432                                 else if (next != ' ' && next != '\t') {
433                                         ungetc(next, fp);
434                                         break;
435                                 }
436                                 if (fgets(buf, sizeof(buf), fp) == NULL)
437                                         break;
438                                 else
439                                         bytes += strlen(buf);
440                         }
441                 } else {
442                         SEND_EXIT_IF_ERROR(sock_puts(sock, buf));
443                         if (buf[0] == '\0')
444                                 break;
445                 }
446         }
447
448         /* output body part */
449         while (fgets(buf, sizeof(buf), fp) != NULL) {
450                 bytes += strlen(buf);
451                 strretchomp(buf);
452
453                 SEND_DIALOG_UPDATE();
454
455                 /* escape when a dot appears on the top */
456                 if (buf[0] == '.')
457                         SEND_EXIT_IF_ERROR(sock_write(sock, ".", 1));
458
459                 SEND_EXIT_IF_ERROR(sock_puts(sock, buf));
460         }
461
462         g_snprintf(str, sizeof(str), _("Sending message (%d / %d bytes)"),
463                    bytes, size);
464         progress_dialog_set_label(dialog->dialog, str);
465         progress_dialog_set_percentage
466                 (dialog->dialog, (gfloat)bytes / (gfloat)size);
467         GTK_EVENTS_FLUSH();
468
469         return 0;
470 }
471
472 #undef EXIT_IF_CANCELLED
473 #undef SEND_EXIT_IF_ERROR
474 #undef SEND_DIALOG_UPDATE
475
476 #if USE_SSL
477 static SockInfo *send_smtp_open(const gchar *server, gushort port,
478                                 const gchar *domain, gboolean use_smtp_auth,
479                                 SSLSMTPType ssl_type)
480 #else
481 static SockInfo *send_smtp_open(const gchar *server, gushort port,
482                                 const gchar *domain, gboolean use_smtp_auth)
483 #endif
484 {
485         SockInfo *sock;
486         gint val;
487
488         g_return_val_if_fail(server != NULL, NULL);
489
490         if ((sock = sock_connect(server, port)) == NULL) {
491                 log_warning(_("Can't connect to SMTP server: %s:%d\n"),
492                             server, port);
493                 return NULL;
494         }
495
496 #if USE_SSL
497         if (ssl_type == SSL_SMTP_TUNNEL && !ssl_init_socket(sock)) {
498                 log_warning(_("SSL connection failed"));
499                 sock_close(sock);
500                 return NULL;
501         }
502 #endif
503
504         if (smtp_ok(sock) != SM_OK) {
505                 log_warning(_("Error occurred while connecting to %s:%d\n"),
506                             server, port);
507                 sock_close(sock);
508                 return NULL;
509         }
510
511 #if USE_SSL
512         val = smtp_helo(sock, domain ? domain : get_domain_name(),
513                         use_smtp_auth || ssl_type == SSL_SMTP_STARTTLS);
514 #else
515         val = smtp_helo(sock, domain ? domain : get_domain_name(),
516                         use_smtp_auth);
517 #endif
518
519         if (val != SM_OK) {
520                 log_warning(_("Error occurred while sending HELO\n"));
521                 sock_close(sock);
522                 return NULL;
523         }
524
525 #if USE_SSL
526         if (ssl_type == SSL_SMTP_STARTTLS) {
527                 val = esmtp_starttls(sock);
528                 if (val != SM_OK) {
529                         log_warning(_("Error occurred while sending STARTTLS\n"));
530                         sock_close(sock);
531                         return NULL;
532                 }
533                 if (!ssl_init_socket_with_method(sock, SSL_METHOD_TLSv1)) {
534                         sock_close(sock);
535                         return NULL;
536                 }
537                 val = esmtp_ehlo(sock, domain ? domain : get_domain_name());
538                 if (val != SM_OK) {
539                         log_warning(_("Error occurred while sending EHLO\n"));
540                         sock_close(sock);
541                         return NULL;
542                 }
543         }
544 #endif
545
546         return sock;
547 }
548
549
550 static SendProgressDialog *send_progress_dialog_create(void)
551 {
552         SendProgressDialog *dialog;
553         ProgressDialog *progress;
554
555         dialog = g_new0(SendProgressDialog, 1);
556
557         progress = progress_dialog_create();
558         gtk_window_set_title(GTK_WINDOW(progress->window),
559                              _("Sending message"));
560         gtk_signal_connect(GTK_OBJECT(progress->cancel_btn), "clicked",
561                            GTK_SIGNAL_FUNC(send_cancel), dialog);
562         gtk_signal_connect(GTK_OBJECT(progress->window), "delete_event",
563                            GTK_SIGNAL_FUNC(gtk_true), NULL);
564         gtk_window_set_modal(GTK_WINDOW(progress->window), TRUE);
565         manage_window_set_transient(GTK_WINDOW(progress->window));
566
567         progress_dialog_set_value(progress, 0.0);
568
569         gtk_widget_show_now(progress->window);
570
571         dialog->dialog = progress;
572         dialog->queue_list = NULL;
573         dialog->cancelled = FALSE;
574
575         return dialog;
576 }
577
578 static void send_progress_dialog_destroy(SendProgressDialog *dialog)
579 {
580         g_return_if_fail(dialog != NULL);
581
582         progress_dialog_destroy(dialog->dialog);
583         g_free(dialog);
584 }
585
586 static void send_cancel(GtkWidget *widget, gpointer data)
587 {
588         SendProgressDialog *dialog = data;
589
590         dialog->cancelled = TRUE;
591 }
592
593 static gchar *send_query_password(const gchar *server, const gchar *user)
594 {
595         gchar *message;
596         gchar *pass;
597
598         message = g_strdup_printf(_("Input password for %s on %s:"),
599                                   user, server);
600         pass = input_dialog_with_invisible(_("Input password"), message, NULL);
601         g_free(message);
602
603         return pass;
604 }