0.8.6claws90
[claws.git] / src / common / smtp.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 <glib.h>
25 #include <stdio.h>
26 #include <string.h>
27
28 #include "intl.h"
29 #include "smtp.h"
30 #include "socket.h"
31 #include "md5.h"
32 #include "base64.h"
33 #include "utils.h"
34 #include "log.h"
35
36 static gint verbose = 1;
37
38 #define UI_UPDATE(session, phase) \
39 { \
40         if (SESSION(session)->ui_func) \
41                 SESSION(session)->ui_func(SESSION(session), phase); \
42 }
43
44 static gint smtp_starttls(SMTPSession *session);
45 static gint smtp_auth_cram_md5(SMTPSession *session, gchar *buf, gint len);
46 static gint smtp_auth_login(SMTPSession *session, gchar *buf, gint len);
47
48 static gint smtp_ok(SockInfo *sock, gchar *buf, gint len);
49
50 Session *smtp_session_new(void)
51 {
52         SMTPSession *session;
53
54         session = g_new0(SMTPSession, 1);
55         SESSION(session)->type             = SESSION_SMTP;
56         SESSION(session)->server           = NULL;
57         SESSION(session)->sock             = NULL;
58         SESSION(session)->connected        = FALSE;
59         SESSION(session)->phase            = SESSION_READY;
60         SESSION(session)->last_access_time = 0;
61         SESSION(session)->data             = NULL;
62
63         SESSION(session)->destroy          = smtp_session_destroy;
64         SESSION(session)->ui_func          = NULL;
65
66         session->avail_auth_type           = 0;
67         session->user                      = NULL;
68         session->pass                      = NULL;
69
70         return SESSION(session);
71 }
72
73 void smtp_session_destroy(Session *session)
74 {
75         sock_close(session->sock);
76         session->sock = NULL;
77
78         g_free(SMTP_SESSION(session)->user);
79         g_free(SMTP_SESSION(session)->pass);
80 }
81
82 #if USE_OPENSSL
83 gint smtp_connect(SMTPSession *session, const gchar *server, gushort port,
84                   const gchar *domain, const gchar *user, const gchar *pass,
85                   SSLType ssl_type)
86 #else
87 gint smtp_connect(SMTPSession *session, const gchar *server, gushort port,
88                   const gchar *domain, const gchar *user, const gchar *pass)
89 #endif
90 {
91         SockInfo *sock;
92         gboolean use_esmtp;
93         SMTPAuthType avail_auth_type = 0;
94         gint val;
95
96         g_return_val_if_fail(session != NULL, SM_ERROR);
97         g_return_val_if_fail(server != NULL, SM_ERROR);
98
99 #if USE_OPENSSL
100         use_esmtp = user != NULL || ssl_type == SSL_STARTTLS;
101 #else
102         use_esmtp = user != NULL;
103 #endif
104
105         SESSION(session)->server = g_strdup(server);
106         session->user = user ? g_strdup(user) : NULL;
107         session->pass = pass ? g_strdup(pass) : user ? g_strdup("") : NULL;
108
109         UI_UPDATE(session, SMTP_CONNECT);
110
111         if ((sock = sock_connect(server, port)) == NULL) {
112                 log_warning(_("Can't connect to SMTP server: %s:%d\n"),
113                             server, port);
114                 return SM_ERROR;
115         }
116
117 #if USE_OPENSSL
118         if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
119                 log_warning(_("SSL connection failed"));
120                 sock_close(sock);
121                 return SM_ERROR;
122         }
123 #endif
124
125         if (smtp_ok(sock, NULL, 0) != SM_OK) {
126                 log_warning(_("Error occurred while connecting to %s:%d\n"),
127                             server, port);
128                 sock_close(sock);
129                 return SM_ERROR;
130         }
131
132         SESSION(session)->sock = sock;
133         SESSION(session)->connected = TRUE;
134
135         if (!domain)
136                 domain = get_domain_name();
137
138         if (use_esmtp)
139                 val = smtp_ehlo(session, domain, &avail_auth_type);
140         else
141                 val = smtp_helo(session, domain);
142         if (val != SM_OK) {
143                 log_warning(_("Error occurred while sending HELO\n"));
144                 return val;
145         }
146
147 #if USE_OPENSSL
148         if (ssl_type == SSL_STARTTLS) {
149                 val = smtp_starttls(session);
150                 if (val != SM_OK) {
151                         log_warning(_("Error occurred while sending STARTTLS\n"));
152                         return val;
153                 }
154                 if (!ssl_init_socket_with_method(sock, SSL_METHOD_TLSv1)) {
155                         return SM_ERROR;
156                 }
157                 val = smtp_ehlo(session, domain, &avail_auth_type);
158                 if (val != SM_OK) {
159                         log_warning(_("Error occurred while sending EHLO\n"));
160                         return val;
161                 }
162         }
163 #endif
164
165         session->avail_auth_type = avail_auth_type;
166
167         return 0;
168 }
169
170 gint smtp_from(SMTPSession *session, const gchar *from)
171 {
172         gchar buf[MSGBUFSIZE];
173
174         g_return_val_if_fail(session != NULL, SM_ERROR);
175         g_return_val_if_fail(from != NULL, SM_ERROR);
176
177         UI_UPDATE(session, SMTP_FROM);
178
179         if (strchr(from, '<'))
180                 g_snprintf(buf, sizeof(buf), "MAIL FROM: %s", from);
181         else
182                 g_snprintf(buf, sizeof(buf), "MAIL FROM: <%s>", from);
183
184         sock_printf(SESSION(session)->sock, "%s\r\n", buf);
185         if (verbose)
186                 log_print("SMTP> %s\n", buf);
187
188         return smtp_ok(SESSION(session)->sock, NULL, 0);
189 }
190
191 gint smtp_auth(SMTPSession *session, SMTPAuthType forced_auth_type)
192 {
193         gchar buf[MSGBUFSIZE];
194         SMTPAuthType authtype = 0;
195         guchar hexdigest[33];
196         gchar *challenge, *response, *response64;
197         gint challengelen;
198         SockInfo *sock;
199
200         g_return_val_if_fail(session != NULL, SM_ERROR);
201         g_return_val_if_fail(session->user != NULL, SM_ERROR);
202
203         UI_UPDATE(session, SMTP_AUTH);
204
205         sock = SESSION(session)->sock;
206
207         if ((forced_auth_type == SMTPAUTH_CRAM_MD5 ||
208              (forced_auth_type == 0 &&
209               (session->avail_auth_type & SMTPAUTH_CRAM_MD5) != 0)) &&
210             smtp_auth_cram_md5(session, buf, sizeof(buf)) == SM_OK)
211                 authtype = SMTPAUTH_CRAM_MD5;
212         else if ((forced_auth_type == SMTPAUTH_LOGIN ||
213                   (forced_auth_type == 0 &&
214                    (session->avail_auth_type & SMTPAUTH_LOGIN) != 0)) &&
215                  smtp_auth_login(session, buf, sizeof(buf)) == SM_OK)
216                 authtype = SMTPAUTH_LOGIN;
217         else {
218                 log_warning(_("SMTP AUTH not available\n"));
219                 return SM_AUTHFAIL;
220         }
221
222         switch (authtype) {
223         case SMTPAUTH_LOGIN:
224                 if (!strncmp(buf, "334 ", 4))
225                         base64_encode(buf, session->user, strlen(session->user));
226                 else
227                         /* Server rejects AUTH */
228                         g_snprintf(buf, sizeof(buf), "*");
229
230                 sock_printf(sock, "%s\r\n", buf);
231                 if (verbose) log_print("ESMTP> [USERID]\n");
232
233                 smtp_ok(sock, buf, sizeof(buf));
234
235                 if (!strncmp(buf, "334 ", 4))
236                         base64_encode(buf, session->pass, strlen(session->pass));
237                 else
238                         /* Server rejects AUTH */
239                         g_snprintf(buf, sizeof(buf), "*");
240
241                 sock_printf(sock, "%s\r\n", buf);
242                 if (verbose) log_print("ESMTP> [PASSWORD]\n");
243                 break;
244         case SMTPAUTH_CRAM_MD5:
245                 if (!strncmp(buf, "334 ", 4)) {
246                         challenge = g_malloc(strlen(buf + 4) + 1);
247                         challengelen = base64_decode(challenge, buf + 4, -1);
248                         challenge[challengelen] = '\0';
249                         if (verbose)
250                                 log_print("ESMTP< [Decoded: %s]\n", challenge);
251
252                         g_snprintf(buf, sizeof(buf), "%s", session->pass);
253                         md5_hex_hmac(hexdigest, challenge, challengelen,
254                                      buf, strlen(session->pass));
255                         g_free(challenge);
256
257                         response = g_strdup_printf
258                                 ("%s %s", session->user, hexdigest);
259                         if (verbose)
260                                 log_print("ESMTP> [Encoded: %s]\n", response);
261
262                         response64 = g_malloc((strlen(response) + 3) * 2 + 1);
263                         base64_encode(response64, response, strlen(response));
264                         g_free(response);
265
266                         sock_printf(sock, "%s\r\n", response64);
267                         if (verbose) log_print("ESMTP> %s\n", response64);
268                         g_free(response64);
269                 } else {
270                         /* Server rejects AUTH */
271                         g_snprintf(buf, sizeof(buf), "*");
272                         sock_printf(sock, "%s\r\n", buf);
273                         if (verbose)
274                                 log_print("ESMTP> %s\n", buf);
275                 }
276                 break;
277         case SMTPAUTH_DIGEST_MD5:
278         default:
279                 /* stop smtp_auth when no correct authtype */
280                 g_snprintf(buf, sizeof(buf), "*");
281                 sock_printf(sock, "%s\r\n", buf);
282                 if (verbose) log_print("ESMTP> %s\n", buf);
283                 break;
284         }
285
286         return smtp_ok(sock, NULL, 0);
287 }
288
289 gint smtp_ehlo(SMTPSession *session, const gchar *hostname,
290                SMTPAuthType *avail_auth_type)
291 {
292         SockInfo *sock;
293         gchar buf[MSGBUFSIZE];
294
295         UI_UPDATE(session, SMTP_EHLO);
296
297         sock = SESSION(session)->sock;
298
299         *avail_auth_type = 0;
300
301         sock_printf(sock, "EHLO %s\r\n", hostname);
302         if (verbose)
303                 log_print("ESMTP> EHLO %s\n", hostname);
304
305         while ((sock_gets(sock, buf, sizeof(buf) - 1)) != -1) {
306                 if (strlen(buf) < 4)
307                         return SM_ERROR;
308                 strretchomp(buf);
309
310                 if (verbose)
311                         log_print("ESMTP< %s\n", buf);
312
313                 if (strncmp(buf, "250-", 4) == 0) {
314                         gchar *p = buf;
315                         p += 4;
316                         if (g_strncasecmp(p, "AUTH", 4) == 0) {
317                                 p += 5;
318                                 if (strcasestr(p, "LOGIN"))
319                                         *avail_auth_type |= SMTPAUTH_LOGIN;
320                                 if (strcasestr(p, "CRAM-MD5"))
321                                         *avail_auth_type |= SMTPAUTH_CRAM_MD5;
322                                 if (strcasestr(p, "DIGEST-MD5"))
323                                         *avail_auth_type |= SMTPAUTH_DIGEST_MD5;
324                         }
325                 } else if ((buf[0] == '1' || buf[0] == '2' || buf[0] == '3') &&
326                     (buf[3] == ' ' || buf[3] == '\0'))
327                         return SM_OK;
328                 else if (buf[3] != '-')
329                         return SM_ERROR;
330                 else if (buf[0] == '5' && buf[1] == '0' &&
331                          (buf[2] == '4' || buf[2] == '3' || buf[2] == '1'))
332                         return SM_ERROR;
333         }
334
335         return SM_UNRECOVERABLE;
336 }
337
338 static gint smtp_starttls(SMTPSession *session)
339 {
340         SockInfo *sock;
341
342         UI_UPDATE(session, SMTP_STARTTLS);
343
344         sock = SESSION(session)->sock;
345
346         sock_printf(sock, "STARTTLS\r\n");
347         if (verbose)
348                 log_print("ESMTP> STARTTLS\n");
349
350         return smtp_ok(sock, NULL, 0);
351 }
352
353 static gint smtp_auth_cram_md5(SMTPSession *session, gchar *buf, gint len)
354 {
355         SockInfo *sock;
356
357         UI_UPDATE(session, SMTP_AUTH);
358
359         sock = SESSION(session)->sock;
360
361         sock_printf(sock, "AUTH CRAM-MD5\r\n");
362         if (verbose)
363                 log_print("ESMTP> AUTH CRAM-MD5\n");
364
365         return smtp_ok(sock, buf, len);
366 }
367
368 static gint smtp_auth_login(SMTPSession *session, gchar *buf, gint len)
369 {
370         SockInfo *sock;
371
372         UI_UPDATE(session, SMTP_AUTH);
373
374         sock = SESSION(session)->sock;
375
376         sock_printf(sock, "AUTH LOGIN\r\n");
377         if (verbose)
378                 log_print("ESMTP> AUTH LOGIN\n");
379
380         return smtp_ok(sock, buf, len);
381 }
382
383 gint smtp_helo(SMTPSession *session, const gchar *hostname)
384 {
385         SockInfo *sock;
386
387         UI_UPDATE(session, SMTP_HELO);
388
389         sock = SESSION(session)->sock;
390
391         sock_printf(sock, "HELO %s\r\n", hostname);
392         if (verbose)
393                 log_print("SMTP> HELO %s\n", hostname);
394
395         return smtp_ok(sock, NULL, 0);
396 }
397
398 gint smtp_rcpt(SMTPSession *session, const gchar *to)
399 {
400         SockInfo *sock;
401         gchar buf[MSGBUFSIZE];
402
403         UI_UPDATE(session, SMTP_RCPT);
404
405         sock = SESSION(session)->sock;
406
407         if (strchr(to, '<'))
408                 g_snprintf(buf, sizeof(buf), "RCPT TO: %s", to);
409         else
410                 g_snprintf(buf, sizeof(buf), "RCPT TO: <%s>", to);
411
412         sock_printf(sock, "%s\r\n", buf);
413         if (verbose)
414                 log_print("SMTP> %s\n", buf);
415
416         return smtp_ok(sock, NULL, 0);
417 }
418
419 gint smtp_data(SMTPSession *session)
420 {
421         SockInfo *sock;
422
423         UI_UPDATE(session, SMTP_DATA);
424
425         sock = SESSION(session)->sock;
426
427         sock_printf(sock, "DATA\r\n");
428         if (verbose)
429                 log_print("SMTP> DATA\n");
430
431         return smtp_ok(sock, NULL, 0);
432 }
433
434 gint smtp_rset(SMTPSession *session)
435 {
436         SockInfo *sock;
437
438         UI_UPDATE(session, SMTP_RSET);
439
440         sock = SESSION(session)->sock;
441
442         sock_printf(sock, "RSET\r\n");
443         if (verbose)
444                 log_print("SMTP> RSET\n");
445
446         return smtp_ok(sock, NULL, 0);
447 }
448
449 gint smtp_quit(SMTPSession *session)
450 {
451         SockInfo *sock;
452
453         UI_UPDATE(session, SMTP_QUIT);
454
455         sock = SESSION(session)->sock;
456
457         sock_printf(sock, "QUIT\r\n");
458         if (verbose)
459                 log_print("SMTP> QUIT\n");
460
461         return smtp_ok(sock, NULL, 0);
462 }
463
464 gint smtp_eom(SMTPSession *session)
465 {
466         SockInfo *sock;
467
468         UI_UPDATE(session, SMTP_EOM);
469
470         sock = SESSION(session)->sock;
471
472         sock_printf(sock, ".\r\n");
473         if (verbose)
474                 log_print("SMTP> . (EOM)\n");
475
476         return smtp_ok(sock, NULL, 0);
477 }
478
479 static gint smtp_ok(SockInfo *sock, gchar *buf, gint len)
480 {
481         gchar tmpbuf[MSGBUFSIZE];
482
483         if (!buf) {
484                 buf = tmpbuf;
485                 len = sizeof(tmpbuf);
486         }
487
488         while ((sock_gets(sock, buf, len - 1)) != -1) {
489                 if (strlen(buf) < 4)
490                         return SM_ERROR;
491                 strretchomp(buf);
492
493                 if (verbose)
494                         log_print("SMTP< %s\n", buf);
495
496                 if ((buf[0] == '1' || buf[0] == '2' || buf[0] == '3') &&
497                     (buf[3] == ' ' || buf[3] == '\0'))
498                         return SM_OK;
499                 else if (buf[3] != '-')
500                         return SM_ERROR;
501                 else if (buf[0] == '5' && buf[1] == '0' &&
502                          (buf[2] == '4' || buf[2] == '3' || buf[2] == '1'))
503                         return SM_ERROR;
504         }
505
506         return SM_UNRECOVERABLE;
507 }