Commit 6eb89fe3 6eb89fe34d47130e40d121fefeac2bf3c03f3a72 by Wojciech Polak

Add ESMTP AUTH support.

* libproto/mailer/smtp.c (smtp_auth, cram_md5): New function.
1 parent 134c0cc6
...@@ -42,12 +42,14 @@ ...@@ -42,12 +42,14 @@
42 #include <mailutils/header.h> 42 #include <mailutils/header.h>
43 #include <mailutils/body.h> 43 #include <mailutils/body.h>
44 #include <mailutils/message.h> 44 #include <mailutils/message.h>
45 #include <mailutils/mime.h>
45 #include <mailutils/mutil.h> 46 #include <mailutils/mutil.h>
46 #include <mailutils/observer.h> 47 #include <mailutils/observer.h>
47 #include <mailutils/property.h> 48 #include <mailutils/property.h>
48 #include <mailutils/stream.h> 49 #include <mailutils/stream.h>
49 #include <mailutils/url.h> 50 #include <mailutils/url.h>
50 #include <mailutils/tls.h> 51 #include <mailutils/tls.h>
52 #include <mailutils/md5.h>
51 #include <mailutils/cctype.h> 53 #include <mailutils/cctype.h>
52 #include <mailutils/cstr.h> 54 #include <mailutils/cstr.h>
53 55
...@@ -115,7 +117,7 @@ struct _smtp ...@@ -115,7 +117,7 @@ struct _smtp
115 SMTP_HELO, SMTP_HELO_ACK, SMTP_QUIT, SMTP_QUIT_ACK, SMTP_ENV_FROM, 117 SMTP_HELO, SMTP_HELO_ACK, SMTP_QUIT, SMTP_QUIT_ACK, SMTP_ENV_FROM,
116 SMTP_ENV_RCPT, SMTP_MAIL_FROM, SMTP_MAIL_FROM_ACK, SMTP_RCPT_TO, 118 SMTP_ENV_RCPT, SMTP_MAIL_FROM, SMTP_MAIL_FROM_ACK, SMTP_RCPT_TO,
117 SMTP_RCPT_TO_ACK, SMTP_DATA, SMTP_DATA_ACK, SMTP_SEND, SMTP_SEND_ACK, 119 SMTP_RCPT_TO_ACK, SMTP_DATA, SMTP_DATA_ACK, SMTP_SEND, SMTP_SEND_ACK,
118 SMTP_SEND_DOT, SMTP_STARTTLS, SMTP_STARTTLS_ACK 120 SMTP_SEND_DOT, SMTP_STARTTLS, SMTP_STARTTLS_ACK, SMTP_AUTH, SMTP_AUTH_ACK,
119 } 121 }
120 state; 122 state;
121 123
...@@ -123,6 +125,7 @@ struct _smtp ...@@ -123,6 +125,7 @@ struct _smtp
123 unsigned long capa; /* Server capabilities */ 125 unsigned long capa; /* Server capabilities */
124 size_t max_size; /* Maximum message size the server is willing 126 size_t max_size; /* Maximum message size the server is willing
125 to accept */ 127 to accept */
128 unsigned long auth_mechs; /* Available ESMTP AUTH mechanisms */
126 129
127 const char *mail_from; 130 const char *mail_from;
128 mu_address_t rcpt_to; /* Destroy this if not the same as argto below. */ 131 mu_address_t rcpt_to; /* Destroy this if not the same as argto below. */
...@@ -148,17 +151,43 @@ typedef struct _smtp *smtp_t; ...@@ -148,17 +151,43 @@ typedef struct _smtp *smtp_t;
148 #define CAPA_STARTTLS 0x00000001 151 #define CAPA_STARTTLS 0x00000001
149 #define CAPA_8BITMIME 0x00000002 152 #define CAPA_8BITMIME 0x00000002
150 #define CAPA_SIZE 0x00000004 153 #define CAPA_SIZE 0x00000004
154 #define CAPA_AUTH 0x00000008
155
156 /* ESMTP AUTH mechanisms */
157 #define AUTH_LOGIN 0x00000001
158 #define AUTH_PLAIN 0x00000002
159 #define AUTH_CRAM_MD5 0x00000004
160 #define AUTH_DIGEST_MD5 0x00000008
161 #define AUTH_GSSAPI 0x00000010
162 #define AUTH_EXTERNAL 0x00000020
163
164 struct auth_mech_record {
165 unsigned long id;
166 char *name;
167 };
168
169 static struct auth_mech_record auth_mech_list[] = {
170 { AUTH_LOGIN, "login" },
171 { AUTH_PLAIN, "plain" },
172 { AUTH_CRAM_MD5, "cram-md5" },
173 { AUTH_DIGEST_MD5, "digest-md5" },
174 { AUTH_GSSAPI, "gssapi" },
175 { AUTH_EXTERNAL, "external" },
176 { 0, NULL },
177 };
151 178
152 static void smtp_destroy (mu_mailer_t); 179 static void smtp_destroy (mu_mailer_t);
153 static int smtp_open (mu_mailer_t, int); 180 static int smtp_open (mu_mailer_t, int);
154 static int smtp_close (mu_mailer_t); 181 static int smtp_close (mu_mailer_t);
155 static int smtp_send_message (mu_mailer_t, mu_message_t, mu_address_t, mu_address_t); 182 static int smtp_send_message (mu_mailer_t, mu_message_t, mu_address_t,
183 mu_address_t);
156 static int smtp_writeline (smtp_t smtp, const char *format, ...); 184 static int smtp_writeline (smtp_t smtp, const char *format, ...);
157 static int smtp_readline (smtp_t); 185 static int smtp_readline (smtp_t);
158 static int smtp_read_ack (smtp_t); 186 static int smtp_read_ack (smtp_t);
159 static int smtp_parse_ehlo_ack (smtp_t); 187 static int smtp_parse_ehlo_ack (smtp_t);
160 static int smtp_write (smtp_t); 188 static int smtp_write (smtp_t);
161 static int smtp_starttls (smtp_t); 189 static int smtp_starttls (smtp_t);
190 static int smtp_auth (smtp_t);
162 191
163 static int _smtp_set_rcpt (smtp_t, mu_message_t, mu_address_t); 192 static int _smtp_set_rcpt (smtp_t, mu_message_t, mu_address_t);
164 193
...@@ -414,6 +443,8 @@ smtp_open (mu_mailer_t mailer, int flags) ...@@ -414,6 +443,8 @@ smtp_open (mu_mailer_t mailer, int flags)
414 mu_stream_close (mailer->stream); 443 mu_stream_close (mailer->stream);
415 return EACCES; 444 return EACCES;
416 } 445 }
446
447 ehlo:
417 status = smtp_writeline (smtp, "EHLO %s\r\n", smtp->localhost); 448 status = smtp_writeline (smtp, "EHLO %s\r\n", smtp->localhost);
418 CHECK_ERROR (smtp, status); 449 CHECK_ERROR (smtp, status);
419 450
...@@ -442,14 +473,26 @@ smtp_open (mu_mailer_t mailer, int flags) ...@@ -442,14 +473,26 @@ smtp_open (mu_mailer_t mailer, int flags)
442 473
443 if (smtp->capa & CAPA_STARTTLS) 474 if (smtp->capa & CAPA_STARTTLS)
444 smtp->state = SMTP_STARTTLS; 475 smtp->state = SMTP_STARTTLS;
476 else if (smtp->capa & CAPA_AUTH && mailer->url->user) {
477 smtp->state = SMTP_AUTH;
478 }
445 else 479 else
446 break; 480 break;
447 } 481 }
448 482
449 case SMTP_STARTTLS: 483 case SMTP_STARTTLS:
450 case SMTP_STARTTLS_ACK: 484 case SMTP_STARTTLS_ACK:
451 smtp_starttls (smtp); 485 if (smtp->capa & CAPA_STARTTLS) {
452 break; 486 smtp_starttls (smtp);
487 goto ehlo;
488 }
489
490 case SMTP_AUTH:
491 case SMTP_AUTH_ACK:
492 if (smtp->capa & CAPA_AUTH) {
493 smtp_auth (smtp);
494 break;
495 }
453 496
454 case SMTP_HELO: 497 case SMTP_HELO:
455 if (!smtp->extended) /* FIXME: this will always be false! */ 498 if (!smtp->extended) /* FIXME: this will always be false! */
...@@ -557,12 +600,13 @@ smtp_starttls (smtp_t smtp) ...@@ -557,12 +600,13 @@ smtp_starttls (smtp_t smtp)
557 #ifdef WITH_TLS 600 #ifdef WITH_TLS
558 int status; 601 int status;
559 mu_mailer_t mailer = smtp->mailer; 602 mu_mailer_t mailer = smtp->mailer;
560 char *keywords[] = { "STARTTLS", "EHLO", NULL }; 603 char *keywords[] = { "STARTTLS", NULL };
561 604
562 if (!mu_tls_enable || !(smtp->capa & CAPA_STARTTLS)) 605 if (!mu_tls_enable || !(smtp->capa & CAPA_STARTTLS))
563 return -1; 606 return -1;
564 607
565 smtp->capa = 0; 608 smtp->capa = 0;
609 smtp->auth_mechs = 0;
566 status = mu_tls_begin (smtp, smtp_reader, smtp_writer, 610 status = mu_tls_begin (smtp, smtp_reader, smtp_writer,
567 smtp_stream_ctl, keywords); 611 smtp_stream_ctl, keywords);
568 612
...@@ -575,6 +619,209 @@ smtp_starttls (smtp_t smtp) ...@@ -575,6 +619,209 @@ smtp_starttls (smtp_t smtp)
575 #endif /* WITH_TLS */ 619 #endif /* WITH_TLS */
576 } 620 }
577 621
622 static void
623 cram_md5 (char *secret, char *challenge, unsigned char *digest)
624 {
625 struct mu_md5_ctx context;
626 unsigned char ipad[64];
627 unsigned char opad[64];
628 int secret_len;
629 int challenge_len;
630 int i;
631
632 if (secret == 0 || challenge == 0)
633 return;
634
635 secret_len = strlen (secret);
636 challenge_len = strlen (challenge);
637 memset (ipad, 0, sizeof (ipad));
638 memset (opad, 0, sizeof (opad));
639
640 if (secret_len > 64)
641 {
642 mu_md5_init_ctx (&context);
643 mu_md5_process_bytes ((unsigned char *)secret, secret_len, &context);
644 mu_md5_finish_ctx (&context, ipad);
645 mu_md5_finish_ctx (&context, opad);
646 }
647 else
648 {
649 memcpy (ipad, secret, secret_len);
650 memcpy (opad, secret, secret_len);
651 }
652
653 for (i = 0; i < 64; i++)
654 {
655 ipad[i] ^= 0x36;
656 opad[i] ^= 0x5c;
657 }
658
659 mu_md5_init_ctx (&context);
660 mu_md5_process_bytes (ipad, sizeof (ipad), &context);
661 mu_md5_process_bytes ((unsigned char *)challenge, challenge_len, &context);
662 mu_md5_finish_ctx (&context, digest);
663
664 mu_md5_init_ctx (&context);
665 mu_md5_process_bytes (opad, sizeof (opad), &context);
666 mu_md5_process_bytes (digest, 16, &context);
667 mu_md5_finish_ctx (&context, digest);
668 }
669
670 static int
671 smtp_auth (smtp_t smtp)
672 {
673 int status;
674 mu_mailer_t mailer = smtp->mailer;
675 struct auth_mech_record *mechs = auth_mech_list;
676 const char *chosen_mech_name = NULL;
677 int chosen_mech_id = 0;
678
679 status = mu_url_sget_auth (mailer->url, &chosen_mech_name);
680 if (status != MU_ERR_NOENT)
681 {
682 for (; mechs->name; mechs++)
683 {
684 if (!mu_c_strcasecmp (mechs->name, chosen_mech_name))
685 {
686 chosen_mech_id = mechs->id;
687 break;
688 }
689 }
690 }
691 if (chosen_mech_id)
692 {
693 if (smtp->auth_mechs & chosen_mech_id)
694 {
695 smtp->auth_mechs = 0;
696 smtp->auth_mechs |= chosen_mech_id;
697 }
698 else
699 {
700 MU_DEBUG1 (mailer->debug, MU_DEBUG_ERROR,
701 "mailer does not support AUTH '%s' mechanism\n",
702 chosen_mech_name);
703 return -1;
704 }
705 }
706
707 #if 0 && defined(WITH_GSASL)
708
709 /* FIXME: Add GNU SASL support. */
710
711 #else
712
713 /* Provide basic AUTH mechanisms when GSASL is not enabled. */
714
715 if (smtp->auth_mechs & AUTH_CRAM_MD5)
716 {
717 int i;
718 char *p, *buf = NULL;
719 const char *user = NULL;
720 mu_secret_t secret;
721 unsigned char *chl;
722 size_t chlen, buflen = 0, b64buflen = 0;
723 unsigned char *b64buf = NULL;
724 unsigned char digest[16];
725 static char ascii_digest[33];
726 memset (digest, 0, 16);
727
728 status = mu_url_sget_user (mailer->url, &user);
729 if (status == MU_ERR_NOENT)
730 return -1;
731
732 status = mu_url_get_secret (mailer->url, &secret);
733 if (status == MU_ERR_NOENT)
734 {
735 MU_DEBUG (mailer->debug, MU_DEBUG_ERROR,
736 "AUTH CRAM-MD5 mechanism requires giving a password\n");
737 return -1;
738 }
739
740 status = smtp_writeline (smtp, "AUTH CRAM-MD5\r\n");
741 CHECK_ERROR (smtp, status);
742 status = smtp_write (smtp);
743 CHECK_EAGAIN (smtp, status);
744 status = smtp_read_ack (smtp);
745 CHECK_EAGAIN (smtp, status);
746
747 if (strncmp (smtp->buffer, "334 ", 4))
748 {
749 MU_DEBUG (mailer->debug, MU_DEBUG_ERROR,
750 "mailer rejected the AUTH CRAM-MD5 command\n");
751 return -1;
752 }
753
754 p = strchr (smtp->buffer, ' ') + 1;
755 mu_rtrim_cset (p, "\r\n");
756 mu_base64_decode (p, strlen (p), &chl, &chlen);
757
758 cram_md5 ((char *)mu_secret_password (secret), chl, digest);
759 mu_secret_password_unref (secret);
760 free (chl);
761
762 for (i = 0; i < 16; i++)
763 sprintf (ascii_digest + 2 * i, "%02x", digest[i]);
764
765 mu_asnprintf (&buf, &buflen, "%s %s", user, ascii_digest);
766 buflen = strlen (buf);
767 mu_base64_encode (buf, buflen, &b64buf, &b64buflen);
768 b64buf[b64buflen] = '\0';
769 free (buf);
770
771 status = smtp_writeline (smtp, "%s\r\n", b64buf);
772 CHECK_ERROR (smtp, status);
773 status = smtp_write (smtp);
774 CHECK_EAGAIN (smtp, status);
775 status = smtp_read_ack (smtp);
776 CHECK_EAGAIN (smtp, status);
777 }
778
779 else if (smtp->auth_mechs & AUTH_PLAIN)
780 {
781 int c;
782 char *buf = NULL;
783 unsigned char *b64buf = NULL;
784 size_t buflen = 0, b64buflen = 0;
785 const char *user = NULL;
786 mu_secret_t secret;
787
788 status = mu_url_sget_user (mailer->url, &user);
789 if (status == MU_ERR_NOENT)
790 return -1;
791
792 status = mu_url_get_secret (mailer->url, &secret);
793 if (status == MU_ERR_NOENT)
794 {
795 MU_DEBUG (mailer->debug, MU_DEBUG_ERROR,
796 "AUTH PLAIN mechanism requires giving a password\n");
797 return -1;
798 }
799
800 mu_asnprintf (&buf, &buflen, "^%s^%s",
801 user, mu_secret_password (secret));
802 mu_secret_password_unref (secret);
803 buflen = strlen (buf);
804 for (c = buflen - 1; c >= 0; c--)
805 {
806 if (buf[c] == '^')
807 buf[c] = '\0';
808 }
809 mu_base64_encode (buf, buflen, &b64buf, &b64buflen);
810 b64buf[b64buflen] = '\0';
811 free (buf);
812
813 status = smtp_writeline (smtp, "AUTH PLAIN %s\r\n", b64buf);
814 CHECK_ERROR (smtp, status);
815 status = smtp_write (smtp);
816 CHECK_EAGAIN (smtp, status);
817 status = smtp_read_ack (smtp);
818 CHECK_EAGAIN (smtp, status);
819 }
820
821 #endif /* not WITH_GSASL */
822 return 0;
823 }
824
578 static int 825 static int
579 message_set_header_value (mu_message_t msg, const char *field, const char *value) 826 message_set_header_value (mu_message_t msg, const char *field, const char *value)
580 { 827 {
...@@ -1157,6 +1404,27 @@ smtp_parse_ehlo_ack (smtp_t smtp) ...@@ -1157,6 +1404,27 @@ smtp_parse_ehlo_ack (smtp_t smtp)
1157 smtp->max_size = n; 1404 smtp->max_size = n;
1158 } 1405 }
1159 } 1406 }
1407 else if (!mu_c_strncasecmp (smtp->buffer, "250-AUTH", 8))
1408 {
1409 char *name, *s;
1410 smtp->capa |= CAPA_AUTH;
1411
1412 for (name = strtok_r (smtp->buffer + 8, " ", &s); name;
1413 name = strtok_r (NULL, " ", &s))
1414 {
1415 struct auth_mech_record *mechs = auth_mech_list;
1416 for (; mechs->name; mechs++)
1417 {
1418 mu_rtrim_cset (name, "\r\n");
1419 if (!mu_c_strcasecmp (mechs->name, name))
1420 {
1421 smtp->auth_mechs |= mechs->id;
1422 break;
1423 }
1424 }
1425 }
1426 }
1427
1160 } 1428 }
1161 } 1429 }
1162 while (multi && status == 0); 1430 while (multi && status == 0);
......