6179443bc01593060ffe062682060c35dfdaedba
[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(use_esmtp?  _("Error occurred while sending EHLO\n"):
144                                         _("Error occurred while sending HELO\n"));
145                 return val;
146         }
147
148 #if USE_OPENSSL
149         /* if we have a user to authenticate and no auth methods, but starttls,
150            try to starttls */
151         if (ssl_type == SSL_NONE && avail_auth_type == SMTPAUTH_TLS_AVAILABLE 
152             && user != NULL)
153                 ssl_type = SSL_STARTTLS;
154
155         if (ssl_type == SSL_STARTTLS) {
156                 val = smtp_starttls(session);
157                 if (val != SM_OK) {
158                         log_warning(_("Error occurred while sending STARTTLS\n"));
159                         return val;
160                 }
161                 if (!ssl_init_socket_with_method(sock, SSL_METHOD_TLSv1)) {
162                         return SM_ERROR;
163                 }
164                 val = smtp_ehlo(session, domain, &avail_auth_type);
165                 if (val != SM_OK) {
166                         log_warning(_("Error occurred while sending EHLO\n"));
167                         return val;
168                 }
169         }
170 #endif
171
172         session->avail_auth_type = avail_auth_type;
173
174         return 0;
175 }
176
177 gint smtp_from(SMTPSession *session, const gchar *from)
178 {
179         gchar buf[MSGBUFSIZE];
180
181         g_return_val_if_fail(session != NULL, SM_ERROR);
182         g_return_val_if_fail(from != NULL, SM_ERROR);
183
184         UI_UPDATE(session, SMTP_FROM);
185
186         if (strchr(from, '<'))
187                 g_snprintf(buf, sizeof(buf), "MAIL FROM: %s", from);
188         else
189                 g_snprintf(buf, sizeof(buf), "MAIL FROM: <%s>", from);
190
191         sock_printf(SESSION(session)->sock, "%s\r\n", buf);
192         if (verbose)
193                 log_print("SMTP> %s\n", buf);
194
195         return smtp_ok(SESSION(session)->sock, NULL, 0);
196 }
197
198 gint smtp_auth(SMTPSession *session, SMTPAuthType forced_auth_type)
199 {
200         gchar buf[MSGBUFSIZE];
201         SMTPAuthType authtype = 0;
202         guchar hexdigest[33];
203         gchar *challenge, *response, *response64;
204         gint challengelen;
205         SockInfo *sock;
206
207         g_return_val_if_fail(session != NULL, SM_ERROR);
208         g_return_val_if_fail(session->user != NULL, SM_ERROR);
209
210         UI_UPDATE(session, SMTP_AUTH);
211
212         sock = SESSION(session)->sock;
213
214         if ((forced_auth_type == SMTPAUTH_CRAM_MD5 ||
215              (forced_auth_type == 0 &&
216               (session->avail_auth_type & SMTPAUTH_CRAM_MD5) != 0)) &&
217             smtp_auth_cram_md5(session, buf, sizeof(buf)) == SM_OK)
218                 authtype = SMTPAUTH_CRAM_MD5;
219         else if ((forced_auth_type == SMTPAUTH_LOGIN ||
220                   (forced_auth_type == 0 &&
221                    (session->avail_auth_type & SMTPAUTH_LOGIN) != 0)) &&
222                  smtp_auth_login(session, buf, sizeof(buf)) == SM_OK)
223                 authtype = SMTPAUTH_LOGIN;
224         else {
225                 log_warning(_("SMTP AUTH not available\n"));
226                 return SM_AUTHFAIL;
227         }
228
229         switch (authtype) {
230         case SMTPAUTH_LOGIN:
231                 if (!strncmp(buf, "334 ", 4))
232                         base64_encode(buf, session->user, strlen(session->user));
233                 else
234                         /* Server rejects AUTH */
235                         g_snprintf(buf, sizeof(buf), "*");
236
237                 sock_printf(sock, "%s\r\n", buf);
238                 if (verbose) log_print("ESMTP> [USERID]\n");
239
240                 smtp_ok(sock, buf, sizeof(buf));
241
242                 if (!strncmp(buf, "334 ", 4))
243                         base64_encode(buf, session->pass, strlen(session->pass));
244                 else
245                         /* Server rejects AUTH */
246                         g_snprintf(buf, sizeof(buf), "*");
247
248                 sock_printf(sock, "%s\r\n", buf);
249                 if (verbose) log_print("ESMTP> [PASSWORD]\n");
250                 break;
251         case SMTPAUTH_CRAM_MD5:
252                 if (!strncmp(buf, "334 ", 4)) {
253                         challenge = g_malloc(strlen(buf + 4) + 1);
254                         challengelen = base64_decode(challenge, buf + 4, -1);
255                         challenge[challengelen] = '\0';
256                         if (verbose)
257                                 log_print("ESMTP< [Decoded: %s]\n", challenge);
258
259                         g_snprintf(buf, sizeof(buf), "%s", session->pass);
260                         md5_hex_hmac(hexdigest, challenge, challengelen,
261                                      buf, strlen(session->pass));
262                         g_free(challenge);
263
264                         response = g_strdup_printf
265                                 ("%s %s", session->user, hexdigest);
266                         if (verbose)
267                                 log_print("ESMTP> [Encoded: %s]\n", response);
268
269                         response64 = g_malloc((strlen(response) + 3) * 2 + 1);
270                         base64_encode(response64, response, strlen(response));
271                         g_free(response);
272
273                         sock_printf(sock, "%s\r\n", response64);
274                         if (verbose) log_print("ESMTP> %s\n", response64);
275                         g_free(response64);
276                 } else {
277                         /* Server rejects AUTH */
278                         g_snprintf(buf, sizeof(buf), "*");
279                         sock_printf(sock, "%s\r\n", buf);
280                         if (verbose)
281                                 log_print("ESMTP> %s\n", buf);
282                 }
283                 break;
284         case SMTPAUTH_DIGEST_MD5:
285         default:
286                 /* stop smtp_auth when no correct authtype */
287                 g_snprintf(buf, sizeof(buf), "*");
288                 sock_printf(sock, "%s\r\n", buf);
289                 if (verbose) log_print("ESMTP> %s\n", buf);
290                 break;
291         }
292
293         return smtp_ok(sock, NULL, 0);
294 }
295
296 gint smtp_ehlo(SMTPSession *session, const gchar *hostname,
297                SMTPAuthType *avail_auth_type)
298 {
299         SockInfo *sock;
300         gchar buf[MSGBUFSIZE];
301
302         UI_UPDATE(session, SMTP_EHLO);
303
304         sock = SESSION(session)->sock;
305
306         *avail_auth_type = 0;
307
308         sock_printf(sock, "EHLO %s\r\n", hostname);
309         if (verbose)
310                 log_print("ESMTP> EHLO %s\n", hostname);
311
312         while ((sock_gets(sock, buf, sizeof(buf) - 1)) != -1) {
313                 if (strlen(buf) < 4)
314                         return SM_ERROR;
315                 strretchomp(buf);
316
317                 if (verbose)
318                         log_print("ESMTP< %s\n", buf);
319
320                 if (strncmp(buf, "250-", 4) == 0) {
321                         gchar *p = buf;
322                         p += 4;
323                         if (g_strncasecmp(p, "AUTH", 4) == 0) {
324                                 p += 5;
325                                 if (strcasestr(p, "LOGIN"))
326                                         *avail_auth_type |= SMTPAUTH_LOGIN;
327                                 if (strcasestr(p, "CRAM-MD5"))
328                                         *avail_auth_type |= SMTPAUTH_CRAM_MD5;
329                                 if (strcasestr(p, "DIGEST-MD5"))
330                                         *avail_auth_type |= SMTPAUTH_DIGEST_MD5;
331                         } else if (g_strncasecmp(p, "STARTTLS", 8) == 0) {
332                                 p += 9;
333                                 *avail_auth_type |= SMTPAUTH_TLS_AVAILABLE;
334                         }
335                 } else if ((buf[0] == '1' || buf[0] == '2' || buf[0] == '3') &&
336                     (buf[3] == ' ' || buf[3] == '\0'))
337                         return SM_OK;
338                 else if (buf[3] != '-')
339                         return SM_ERROR;
340                 else if (buf[0] == '5' && buf[1] == '0' &&
341                          (buf[2] == '4' || buf[2] == '3' || buf[2] == '1'))
342                         return SM_ERROR;
343         }
344
345         return SM_UNRECOVERABLE;
346 }
347
348 static gint smtp_starttls(SMTPSession *session)
349 {
350         SockInfo *sock;
351
352         UI_UPDATE(session, SMTP_STARTTLS);
353
354         sock = SESSION(session)->sock;
355
356         sock_printf(sock, "STARTTLS\r\n");
357         if (verbose)
358                 log_print("ESMTP> STARTTLS\n");
359
360         return smtp_ok(sock, NULL, 0);
361 }
362
363 static gint smtp_auth_cram_md5(SMTPSession *session, gchar *buf, gint len)
364 {
365         SockInfo *sock;
366
367         UI_UPDATE(session, SMTP_AUTH);
368
369         sock = SESSION(session)->sock;
370
371         sock_printf(sock, "AUTH CRAM-MD5\r\n");
372         if (verbose)
373                 log_print("ESMTP> AUTH CRAM-MD5\n");
374
375         return smtp_ok(sock, buf, len);
376 }
377
378 static gint smtp_auth_login(SMTPSession *session, gchar *buf, gint len)
379 {
380         SockInfo *sock;
381
382         UI_UPDATE(session, SMTP_AUTH);
383
384         sock = SESSION(session)->sock;
385
386         sock_printf(sock, "AUTH LOGIN\r\n");
387         if (verbose)
388                 log_print("ESMTP> AUTH LOGIN\n");
389
390         return smtp_ok(sock, buf, len);
391 }
392
393 gint smtp_helo(SMTPSession *session, const gchar *hostname)
394 {
395         SockInfo *sock;
396
397         UI_UPDATE(session, SMTP_HELO);
398
399         sock = SESSION(session)->sock;
400
401         sock_printf(sock, "HELO %s\r\n", hostname);
402         if (verbose)
403                 log_print("SMTP> HELO %s\n", hostname);
404
405         return smtp_ok(sock, NULL, 0);
406 }
407
408 gint smtp_rcpt(SMTPSession *session, const gchar *to)
409 {
410         SockInfo *sock;
411         gchar buf[MSGBUFSIZE];
412
413         UI_UPDATE(session, SMTP_RCPT);
414
415         sock = SESSION(session)->sock;
416
417         if (strchr(to, '<'))
418                 g_snprintf(buf, sizeof(buf), "RCPT TO: %s", to);
419         else
420                 g_snprintf(buf, sizeof(buf), "RCPT TO: <%s>", to);
421
422         sock_printf(sock, "%s\r\n", buf);
423         if (verbose)
424                 log_print("SMTP> %s\n", buf);
425
426         return smtp_ok(sock, NULL, 0);
427 }
428
429 gint smtp_data(SMTPSession *session)
430 {
431         SockInfo *sock;
432
433         UI_UPDATE(session, SMTP_DATA);
434
435         sock = SESSION(session)->sock;
436
437         sock_printf(sock, "DATA\r\n");
438         if (verbose)
439                 log_print("SMTP> DATA\n");
440
441         return smtp_ok(sock, NULL, 0);
442 }
443
444 gint smtp_rset(SMTPSession *session)
445 {
446         SockInfo *sock;
447
448         UI_UPDATE(session, SMTP_RSET);
449
450         sock = SESSION(session)->sock;
451
452         sock_printf(sock, "RSET\r\n");
453         if (verbose)
454                 log_print("SMTP> RSET\n");
455
456         return smtp_ok(sock, NULL, 0);
457 }
458
459 gint smtp_quit(SMTPSession *session)
460 {
461         SockInfo *sock;
462
463         UI_UPDATE(session, SMTP_QUIT);
464
465         sock = SESSION(session)->sock;
466
467         sock_printf(sock, "QUIT\r\n");
468         if (verbose)
469                 log_print("SMTP> QUIT\n");
470
471         return smtp_ok(sock, NULL, 0);
472 }
473
474 gint smtp_eom(SMTPSession *session)
475 {
476         SockInfo *sock;
477
478         UI_UPDATE(session, SMTP_EOM);
479
480         sock = SESSION(session)->sock;
481
482         sock_printf(sock, ".\r\n");
483         if (verbose)
484                 log_print("SMTP> . (EOM)\n");
485
486         return smtp_ok(sock, NULL, 0);
487 }
488
489 static gint smtp_ok(SockInfo *sock, gchar *buf, gint len)
490 {
491         gchar tmpbuf[MSGBUFSIZE];
492
493         if (!buf) {
494                 buf = tmpbuf;
495                 len = sizeof(tmpbuf);
496         }
497
498         while ((sock_gets(sock, buf, len - 1)) != -1) {
499                 if (strlen(buf) < 4)
500                         return SM_ERROR;
501                 strretchomp(buf);
502
503                 if (verbose)
504                         log_print("SMTP< %s\n", buf);
505
506                 if ((buf[0] == '1' || buf[0] == '2' || buf[0] == '3') &&
507                     (buf[3] == ' ' || buf[3] == '\0'))
508                         return SM_OK;
509                 else if (buf[3] != '-')
510                         return SM_ERROR;
511                 else if (buf[0] == '5' && buf[1] == '0' &&
512                          (buf[2] == '4' || buf[2] == '3' || buf[2] == '1'))
513                         return SM_ERROR;
514         }
515
516         return SM_UNRECOVERABLE;
517 }