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