3 * $Id: smtpauth.c,v 1.14 2009/06/12 08:55:50 taizo Exp $
4 * Copyright (C) 2009 HDE, Inc.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or (at your option)
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GNU Emacs; see the file COPYING. If not, write to
18 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include <sys/types.h>
28 #include <sys/socket.h>
29 #include <sys/utsname.h>
30 #include <netinet/in.h>
31 #include <arpa/inet.h>
34 #include <sys/ioctl.h>
41 #define EHLO_CMD ("EHLO ") /* ESMTP ehlo command */
42 #define AUTH_CMD ("AUTH ") /* ESMTP auth command */
43 #define QUIT_CMD ("QUIT ") /* ESMTP quit command */
46 #define RESP_IERROR "internal error"
47 #define RESP_UNAVAILABLE "remote authentication server is currently unavailable"
48 #define RESP_UNEXPECTED "unexpected response from remote authentication server"
49 #define RESP_SYNCERROR "error synchronizing with remote authentication server"
50 #define RESP_CREDERROR "remote authentication server rejected your credentials"
55 #define AUTH_PLAIN 1 << 0
56 #define AUTH_LOGIN 1 << 1
57 #define AUTH_CRAM_MD5 1 << 2
58 #define AUTH_DIGEST_MD5 1 << 3
60 #define B64(c) (isascii(c) ? base64val[(int)(c)] : -1)
63 md5_hex_hmac(char *hexdigest, unsigned char* text, unsigned int text_len, unsigned char* key, unsigned int key_len);
65 hmac_md5(unsigned char* text, unsigned int text_len, unsigned char* key, unsigned int key_len, unsigned char *digest);
68 static const char base64char[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
69 static const char base64val[128] = {
70 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
71 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
72 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
73 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
74 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
75 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
76 -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
77 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
82 md5_hex_hmac(char *hexdigest, unsigned char* text, unsigned int text_len, unsigned char* key, unsigned int key_len) {
84 unsigned char digest[16];
87 hmac_md5(text, text_len, key, key_len, digest);
88 for(cnt=0; cnt<16; cnt++) {
89 sprintf(hexdigest + 2 * cnt, "%02x", digest[cnt]);
95 hmac_md5(unsigned char* text, unsigned int text_len, unsigned char* key, unsigned int key_len, unsigned char *digest) {
98 unsigned char k_ipad[64];
99 unsigned char k_opad[64];
102 memset(k_ipad, 0, sizeof k_ipad);
103 memset(k_opad, 0, sizeof k_opad);
108 MD5Update(&tctx, key, key_len);
109 MD5Final(k_ipad, &tctx);
110 MD5Final(k_opad, &tctx);
112 memcpy(k_ipad, key, key_len);
113 memcpy(k_opad, key, key_len);
116 for(cnt=0; cnt<64; cnt++) {
122 MD5Update(&context, k_ipad, 64);
123 MD5Update(&context, text, text_len);
124 MD5Final(digest, &context);
127 MD5Update(&context, k_opad, 64);
128 MD5Update(&context, digest, 16);
129 MD5Final(digest, &context);
134 base64_encode(char *out, const char *in, int inlen) {
136 const char *inp = in;
140 *outp++ = base64char[(inp[0] >> 2) & 0x3f];
141 *outp++ = base64char[((inp[0] & 0x03) << 4) | ((inp[1] >> 4) & 0x0f)];
142 *outp++ = base64char[((inp[1] & 0x0f) << 2) | ((inp[2] >> 6) & 0x03)];
143 *outp++ = base64char[inp[2] & 0x3f];
148 *outp++ = base64char[(inp[0] >> 2) & 0x3f];
150 *outp++ = base64char[(inp[0] & 0x03) << 4];
154 *outp++ = base64char[((inp[0] & 0x03) << 4) | ((inp[1] >> 4) & 0x0f)];
155 *outp++ = base64char[((inp[1] & 0x0f) << 2)];
164 base64_decode(char *out, const char *in, int inlen) {
166 const char *inp = in;
173 while(inlen >=4 && *inp != '\0') {
176 if( B64(buf[0]) == -1) break;
180 if(B64(buf[1]) == -1) break;
184 if(buf[2] != '=' && B64(buf[2]) == -1) break;
188 if(buf[3] != '=' && B64(buf[3]) == -1) break;
190 *outp++ = ((B64(buf[0]) << 2) & 0xfc) | ((B64(buf[1]) >> 4) & 0x03);
192 *outp++ = ((B64(buf[1]) & 0x0f) << 4) | ((B64(buf[2]) >> 2) & 0x0f);
194 *outp++ = ((B64(buf[2]) & 0x03) << 6) | (B64(buf[3]) & 0x3f);
203 retry_writev(int fd, struct iovec *iov, int iovcnt) {
214 while(iovcnt && iov[0].iov_len == 0) {
221 n = writev(fd, iov, iovcnt > iov_max ? iov_max : iovcnt);
223 if(errno == EINVAL && iov_max > 10) {
235 for(cnt=0; cnt<iovcnt; cnt++) {
236 if((int)iov[cnt].iov_len > n) {
237 iov[cnt].iov_base = (char *)iov[cnt].iov_base + n;
238 iov[cnt].iov_len -= n;
241 n -= iov[cnt].iov_len;
242 iov[cnt].iov_len = 0;
253 smtp_auth (config_t * cfg) {
256 struct sockaddr_in addr;
266 int avail_auth_type = 0;
268 struct utsname h_name[1];
272 struct sockaddr_in taddr;
274 struct ifconf ifconf;
275 struct ifreq *ifr, ifreq;
276 unsigned char *ifptr;
281 if(!global.password) {
282 global.password = getpass("Password:");
283 if(!global.password) {
286 if(!*global.password) {
287 global.password = NULL;
290 global.password = strdup(global.password);
292 cfg->password = strdup(global.password);
295 assert(cfg->username != NULL);
296 assert(cfg->password != NULL);
298 smtp = calloc(1, sizeof (smtp_t));
299 smtp->sock = calloc(1, sizeof (socket_t));
300 smtp->buf = calloc(1, sizeof (buffer_t));
301 smtp->buf->sock = smtp->sock;
305 /* open connection to SMTP server */
306 memset(&addr, 0, sizeof (addr));
307 addr.sin_port = htons(cfg->port);
308 addr.sin_family = AF_INET;
309 he = gethostbyname(cfg->host);
312 strcpy(msgbuf, "Error: resolving hostname ");
313 strcat(msgbuf, cfg->host);
314 smtp->error_message = malloc(strlen(msgbuf) + 1);
315 strcpy(smtp->error_message, msgbuf);
319 if((sd = socket(PF_INET, SOCK_DGRAM, 0)) != -1) {
320 bzero(&ifconf, sizeof(struct ifconf));
321 bzero(&ifreq, sizeof(struct ifreq));
322 iflen = 10 * sizeof(struct ifreq);
323 ifptr = malloc(iflen);
324 ifconf.ifc_len = iflen;
325 ifconf.ifc_ifcu.ifcu_req = (struct ifreq *)ifptr;
326 if(ioctl(sd, SIOCGIFCONF, &ifconf) != -1) {
327 for(iflen=sizeof(struct ifreq); iflen<=ifconf.ifc_len; iflen+=sizeof(struct ifreq)) {
328 ifr = (struct ifreq *)ifptr;
329 strcpy(ifreq.ifr_ifrn.ifrn_name, ifr->ifr_name);
330 if(ioctl(sd, SIOCGIFADDR, &ifreq) != -1) {
332 while(he->h_addr_list[n]) {
333 if(he->h_addrtype == AF_INET) {
334 memset((char*)&taddr, 0, sizeof(taddr));
335 memcpy((char*)&taddr.sin_addr, he->h_addr_list[n], he->h_length);
337 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): my ip: %s",
338 inet_ntoa(((struct sockaddr_in *)&ifreq.ifr_addr)->sin_addr));
339 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): smtp ip: %s",
340 inet_ntoa(taddr.sin_addr));
342 if(((struct sockaddr_in *)&ifreq.ifr_addr)->sin_addr.s_addr == taddr.sin_addr.s_addr) {
344 strcpy(msgbuf, "Error: this host is specified. ");
345 strcat(msgbuf, inet_ntoa(taddr.sin_addr));
346 smtp->error_message = malloc(strlen(msgbuf) + 1);
347 strcpy(smtp->error_message, msgbuf);
354 ifptr += sizeof(struct ifreq);
360 addr.sin_addr.s_addr = *((int *)he->h_addr_list[0]);
361 s = socket(PF_INET, SOCK_STREAM, 0);
362 if(connect (s, (struct sockaddr *) &addr, sizeof (addr))) {
364 strcpy(msgbuf, "Error: connecting to ");
365 strcat(msgbuf, inet_ntoa(addr.sin_addr));
366 smtp->error_message = malloc(strlen(msgbuf) + 1);
367 strcpy(smtp->error_message, msgbuf);
372 /* CLAIM: we now have a TCP connection to the remote SMTP server */
374 rc = read(s, rbuf, sizeof(rbuf));
378 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (banner): %m");
381 strcpy(msgbuf, RESP_SYNCERROR);
382 smtp->error_message = malloc(strlen(msgbuf) + 1);
383 strcpy(smtp->error_message, msgbuf);
387 c = strpbrk(rbuf, "\r\n");
392 if(strncmp(rbuf, "220 ", sizeof("220 ")-1)) {
394 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): unexpected response during initial handshake: %s", rbuf);
397 strcpy(msgbuf, RESP_UNEXPECTED);
398 smtp->error_message = malloc(strlen(msgbuf) + 1);
399 strcpy(smtp->error_message, msgbuf);
403 if((uname(h_name)) < 0){
404 myhostname = "localhost.localdomain";
406 myhostname = h_name->nodename;
409 iov[0].iov_base = EHLO_CMD;
410 iov[0].iov_len = sizeof(EHLO_CMD) - 1;
411 iov[1].iov_base = myhostname;
412 iov[1].iov_len = strlen(myhostname);
413 iov[2].iov_base = "\r\n";
414 iov[2].iov_len = sizeof("\r\n") - 1;
417 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): sending %s%s", EHLO_CMD, myhostname);
420 rc = retry_writev(s, iov, 3);
421 memset(iov, 0, sizeof(iov));
425 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): writev: %m");
428 strcpy(msgbuf, RESP_IERROR);
429 smtp->error_message = malloc(strlen(msgbuf) + 1);
430 strcpy(smtp->error_message, msgbuf);
434 /* read and parse the EHLO response */
436 rc = read(s, rbuf, sizeof(rbuf));
440 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (response): %m");
443 strcpy(msgbuf, RESP_IERROR);
444 smtp->error_message = malloc(strlen(msgbuf) + 1);
445 strcpy(smtp->error_message, msgbuf);
449 if(tbuf = strstr(rbuf, "250-AUTH")) {
450 if(strncmp(tbuf, "250", sizeof("250")-1) == 0) {
453 if (*p == '-' || *p == ' ') p++;
454 if (strncasecmp(p, "AUTH", sizeof("AUTH")-1) == 0) {
456 if (strcasestr(p, "PLAIN"))
457 avail_auth_type |= AUTH_PLAIN;
458 if (strcasestr(p, "LOGIN"))
459 avail_auth_type |= AUTH_LOGIN;
460 if (strcasestr(p, "CRAM-MD5"))
461 avail_auth_type |= AUTH_CRAM_MD5;
462 if (strcasestr(p, "DIGEST-MD5"))
463 avail_auth_type |= AUTH_DIGEST_MD5;
468 if (avail_auth_type == 0) {
470 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): smtp authentication is not implemented: %s", rbuf);
473 strcpy(msgbuf, RESP_UNEXPECTED);
474 smtp->error_message = malloc(strlen(msgbuf) + 1);
475 strcpy(smtp->error_message, msgbuf);
479 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): auth_type: %d", avail_auth_type);
482 /* build the AUTH command */
483 if (avail_auth_type & AUTH_CRAM_MD5) {
484 auth = auth_cram_md5(s,&global);
486 else if ((avail_auth_type & AUTH_LOGIN) != 0) {
487 auth = auth_login(s,&global);
489 else if ((avail_auth_type & AUTH_PLAIN) != 0) {
490 auth = auth_plain(s,&global);
494 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): smtp authentication is not implemented: %s", rbuf);
497 strcpy(msgbuf, RESP_UNEXPECTED);
498 smtp->error_message = malloc(strlen(msgbuf) + 1);
499 strcpy(smtp->error_message, msgbuf);
504 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth) auth: [%d]", auth);
508 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth) rejected: [%s]", global.username);
511 strcpy(msgbuf, RESP_CREDERROR);
512 smtp->error_message = malloc(strlen(msgbuf) + 1);
513 strcpy(smtp->error_message, msgbuf);
517 smtp_quit(s,&global);
521 smtp_quit(s,&global);
524 else if(smtp->error == 2)
530 smtp_quit(int s, config_t * cfg) {
535 iov[0].iov_base = QUIT_CMD;
536 iov[0].iov_len = sizeof(QUIT_CMD) - 1;
537 iov[1].iov_base = "\r\n";
538 iov[1].iov_len = sizeof("\r\n") - 1;
541 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): sending %s", QUIT_CMD);
544 rc = retry_writev(s, iov, 2);
545 memset(iov, 0, sizeof(iov));
549 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): quit writev: %m");
558 auth_cram_md5(int s, config_t * cfg) {
566 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): AUTH CRAM-MD5");
568 iov[0].iov_base = AUTH_CMD;
569 iov[0].iov_len = sizeof(AUTH_CMD) - 1;
570 iov[1].iov_base = "CRAM-MD5";
571 iov[1].iov_len = strlen("CRAM-MD5");
572 iov[2].iov_base = "\r\n";
573 iov[2].iov_len = sizeof("\r\n") - 1;
576 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): sending %s%s", AUTH_CMD, "CRAM-MD5");
579 rc = retry_writev(s, iov, 3);
580 memset(iov, 0, sizeof(iov));
584 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): cram-md5 writev: %m");
590 rc = read(s, rbuf, sizeof(rbuf));
594 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (response): %m");
600 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (response): %s",rbuf);
602 if(strncmp(rbuf, "334 ", sizeof("334 ")-1) == 0) {
607 unsigned char hexdigest[33];
609 challenge = malloc(strlen(rbuf + 4) + 1);
610 challengelen = base64_decode(challenge, rbuf + 4, -1);
611 challenge[challengelen] = '\0';
613 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): challenge=%s", challenge);
616 snprintf(buf, sizeof(buf), "%s", cfg->password);
617 md5_hex_hmac(hexdigest, challenge, challengelen, buf, strlen(cfg->password));
620 response = malloc(sizeof(char)*128);
621 sprintf(response, "%s %s", cfg->username, hexdigest);
622 response64 = malloc((strlen(response) + 3) * 2 + 1);
623 base64_encode(response64, response, strlen(response));
626 iov[0].iov_base = response64;
627 iov[0].iov_len = strlen(response64);
628 iov[1].iov_base = "\r\n";
629 iov[1].iov_len = sizeof("\r\n") - 1;
632 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): sending %s", response64);
635 rc = retry_writev(s, iov, 2);
636 memset(iov, 0, sizeof(iov));
640 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): cram-md5 writev: %m");
646 rc = read(s, rbuf, sizeof(rbuf));
650 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (response): %m");
656 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (response): %s",rbuf);
658 if(strncmp(rbuf, "235 ", sizeof("235 ")-1) != 0) {
660 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): auth failure.");
667 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): it seems cram-md5 mech is not implemented.");
676 auth_login(int s, config_t * cfg) {
681 //char buf[RESP_LEN];
685 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): AUTH LOGIN");
687 iov[0].iov_base = AUTH_CMD;
688 iov[0].iov_len = sizeof(AUTH_CMD) - 1;
689 iov[1].iov_base = "LOGIN";
690 iov[1].iov_len = strlen("LOGIN");
691 iov[2].iov_base = "\r\n";
692 iov[2].iov_len = sizeof("\r\n") - 1;
695 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): sending %s%s", AUTH_CMD, "LOGIN");
698 rc = retry_writev(s, iov, 3);
699 memset(iov, 0, sizeof(iov));
703 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): login writev: %m");
709 rc = read(s, rbuf, sizeof(rbuf));
713 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (response): %m");
719 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (response): %s",rbuf);
721 if(strncmp(rbuf, "334 ", sizeof("334 ")-1) == 0) {
722 buf = malloc(sizeof(char)*128);
723 base64_encode(buf, cfg->username, strlen(cfg->username));
725 iov[0].iov_base = buf;
726 iov[0].iov_len = strlen(buf);
727 iov[1].iov_base = "\r\n";
728 iov[1].iov_len = sizeof("\r\n") - 1;
731 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): sending %s", buf);
734 rc = retry_writev(s, iov, 2);
735 memset(iov, 0, sizeof(iov));
739 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): login writev: %m");
745 rc = read(s, rbuf, sizeof(rbuf));
749 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (response): %m");
755 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (response): %s",rbuf);
757 if (strncmp(rbuf, "334 ", sizeof("334 ")-1) == 0) {
758 buf = malloc(sizeof(char)*128);
759 base64_encode(buf, cfg->password, strlen(cfg->password));
761 iov[0].iov_base = buf;
762 iov[0].iov_len = strlen(buf);
763 iov[1].iov_base = "\r\n";
764 iov[1].iov_len = sizeof("\r\n") - 1;
767 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): sending %s", buf);
770 rc = retry_writev(s, iov, 2);
771 memset(iov, 0, sizeof(iov));
775 syslog(LOG_WARNING, "pam_smtpauth(smtpauth): login writev: %m");
781 rc = read(s, rbuf, sizeof(rbuf));
785 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (response): %m");
791 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (response): %s",rbuf);
793 if(strncmp(rbuf, "235 ", sizeof("235 ")-1) != 0) {
795 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): auth failure.");
801 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): it seems login mech is not implemented.");
807 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): it seems login mech is not implemented.");
816 auth_plain(int s, config_t * cfg) {
821 //char buf[RESP_LEN];
827 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): AUTH PLAIN");
829 sprintf(phrase,"%s^%s^%s", cfg->username, cfg->username, cfg->password);
830 len = strlen(phrase);
831 for(cnt=len-1; cnt>=0; cnt--) {
832 if(phrase[cnt] == '^') {
836 buf = malloc(sizeof(char)*128);
837 base64_encode(buf, phrase, len);
839 iov[0].iov_base = AUTH_CMD;
840 iov[0].iov_len = sizeof(AUTH_CMD) - 1;
841 iov[1].iov_base = "PLAIN ";
842 iov[1].iov_len = strlen("PLAIN ");
843 iov[2].iov_base = buf;
844 iov[2].iov_len = strlen(buf);
845 iov[3].iov_base = "\r\n";
846 iov[3].iov_len = sizeof("\r\n") - 1;
849 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): sending %s%s %s", AUTH_CMD, "PLAIN", buf);
852 rc = retry_writev(s, iov, 4);
853 memset(iov, 0, sizeof(iov));
858 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): plain writev: %m");
864 rc = read(s, rbuf, sizeof(rbuf));
868 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (response): %m");
873 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): read (response): %s",rbuf);
874 if(strncmp(rbuf, "235 ", sizeof("235 ")-1) != 0) {
876 syslog(LOG_DEBUG, "pam_smtpauth(smtpauth): auth failure.");