Add ESMTP AUTH support.
* libproto/mailer/smtp.c (smtp_auth, cram_md5): New function.
Showing
1 changed file
with
273 additions
and
5 deletions
... | @@ -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); | ... | ... |
-
Please register or sign in to post a comment