Mercurial > hg > nginx
comparison src/mail/ngx_mail_handler.c @ 9290:4538c1ffb0f8
Mail: added support for XOAUTH2 and OAUTHBEARER authentication.
This patch adds support for the OAUTHBEARER SASL mechanism as defined
by RFC 7628, as well as pre-RFC XOAUTH2 SASL mechanism. For both
mechanisms, the "Auth-User" header is set to the client identity
obtained from the initial SASL response sent by the client, and the
"Auth-Pass" header is set to the Bearer token itself.
The auth server may return the "Auth-Error-SASL" header, which is
passed to the client as an additional SASL challenge. It is expected
to contain mechanism-specific error details, base64-encoded. After
the client responds (with an empty SASL response for XAUTH2, or with
"AQ==" dummy response for OAUTHBEARER), the error message from the
"Auth-Status" header is sent.
Based on a patch by Rob Mueller.
author | Maxim Dounin <mdounin@mdounin.ru> |
---|---|
date | Mon, 03 Jun 2024 18:03:11 +0300 |
parents | f83cb031a4a4 |
children |
comparison
equal
deleted
inserted
replaced
9289:20017bff0de8 | 9290:4538c1ffb0f8 |
---|---|
753 | 753 |
754 return NGX_DONE; | 754 return NGX_DONE; |
755 } | 755 } |
756 | 756 |
757 | 757 |
758 ngx_int_t | |
759 ngx_mail_auth_xoauth2(ngx_mail_session_t *s, ngx_connection_t *c, ngx_uint_t n) | |
760 { | |
761 u_char *p, *last; | |
762 ngx_str_t *arg, oauth; | |
763 | |
764 arg = s->args.elts; | |
765 | |
766 if (s->auth_err.len) { | |
767 ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, | |
768 "mail auth xoauth2 cancel"); | |
769 | |
770 if (s->args.nelts == 1 && arg[0].len == 0) { | |
771 s->out = s->auth_err; | |
772 s->quit = s->auth_quit; | |
773 s->state = 0; | |
774 s->mail_state = 0; | |
775 ngx_str_null(&s->auth_err); | |
776 return NGX_OK; | |
777 } | |
778 | |
779 s->quit = s->auth_quit; | |
780 ngx_str_null(&s->auth_err); | |
781 | |
782 return NGX_MAIL_PARSE_INVALID_COMMAND; | |
783 } | |
784 | |
785 ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0, | |
786 "mail auth xoauth2: \"%V\"", &arg[n]); | |
787 | |
788 oauth.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[n].len)); | |
789 if (oauth.data == NULL) { | |
790 return NGX_ERROR; | |
791 } | |
792 | |
793 if (ngx_decode_base64(&oauth, &arg[n]) != NGX_OK) { | |
794 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
795 "client sent invalid base64 encoding in " | |
796 "AUTH XOAUTH2 command"); | |
797 return NGX_MAIL_PARSE_INVALID_COMMAND; | |
798 } | |
799 | |
800 /* | |
801 * https://developers.google.com/gmail/imap/xoauth2-protocol | |
802 * "user=" {User} "^Aauth=Bearer " {token} "^A^A" | |
803 */ | |
804 | |
805 p = oauth.data; | |
806 last = p + oauth.len; | |
807 | |
808 while (p < last) { | |
809 if (*p++ == '\1') { | |
810 s->login.len = p - oauth.data - 1; | |
811 s->login.data = oauth.data; | |
812 s->passwd.len = last - p; | |
813 s->passwd.data = p; | |
814 break; | |
815 } | |
816 } | |
817 | |
818 if (s->login.len < sizeof("user=") - 1 | |
819 || ngx_strncasecmp(s->login.data, (u_char *) "user=", | |
820 sizeof("user=") - 1) | |
821 != 0) | |
822 { | |
823 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
824 "client sent invalid login in AUTH XOAUTH2 command"); | |
825 return NGX_MAIL_PARSE_INVALID_COMMAND; | |
826 } | |
827 | |
828 s->login.len -= sizeof("user=") - 1; | |
829 s->login.data += sizeof("user=") - 1; | |
830 | |
831 if (s->passwd.len < sizeof("auth=Bearer ") - 1 | |
832 || ngx_strncasecmp(s->passwd.data, (u_char *) "auth=Bearer ", | |
833 sizeof("auth=Bearer ") - 1) | |
834 != 0) | |
835 { | |
836 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
837 "client sent invalid token in AUTH XOAUTH2 command"); | |
838 return NGX_MAIL_PARSE_INVALID_COMMAND; | |
839 } | |
840 | |
841 s->passwd.len -= sizeof("auth=Bearer ") - 1; | |
842 s->passwd.data += sizeof("auth=Bearer ") - 1; | |
843 | |
844 if (s->passwd.len < 2 | |
845 || s->passwd.data[s->passwd.len - 2] != '\1' | |
846 || s->passwd.data[s->passwd.len - 1] != '\1') | |
847 { | |
848 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
849 "client sent invalid token in AUTH XOAUTH2 command"); | |
850 return NGX_MAIL_PARSE_INVALID_COMMAND; | |
851 } | |
852 | |
853 s->passwd.len -= 2; | |
854 | |
855 ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0, | |
856 "mail auth xoauth2: \"%V\" \"%V\"", &s->login, &s->passwd); | |
857 | |
858 s->auth_method = NGX_MAIL_AUTH_XOAUTH2; | |
859 | |
860 return NGX_DONE; | |
861 } | |
862 | |
863 | |
864 ngx_int_t | |
865 ngx_mail_auth_oauthbearer(ngx_mail_session_t *s, ngx_connection_t *c, | |
866 ngx_uint_t n) | |
867 { | |
868 u_char *p, *d, *last, *prev; | |
869 ngx_str_t *arg, oauth; | |
870 | |
871 arg = s->args.elts; | |
872 | |
873 if (s->auth_err.len) { | |
874 ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, | |
875 "mail auth oauthbearer cancel"); | |
876 | |
877 if (s->args.nelts == 1 | |
878 && ngx_strncmp(arg[0].data, (u_char *) "AQ==", 4) == 0) | |
879 { | |
880 s->out = s->auth_err; | |
881 s->quit = s->auth_quit; | |
882 s->state = 0; | |
883 s->mail_state = 0; | |
884 ngx_str_null(&s->auth_err); | |
885 return NGX_OK; | |
886 } | |
887 | |
888 s->quit = s->auth_quit; | |
889 ngx_str_null(&s->auth_err); | |
890 | |
891 return NGX_MAIL_PARSE_INVALID_COMMAND; | |
892 } | |
893 | |
894 ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0, | |
895 "mail auth oauthbearer: \"%V\"", &arg[n]); | |
896 | |
897 oauth.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[n].len)); | |
898 if (oauth.data == NULL) { | |
899 return NGX_ERROR; | |
900 } | |
901 | |
902 if (ngx_decode_base64(&oauth, &arg[n]) != NGX_OK) { | |
903 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
904 "client sent invalid base64 encoding in " | |
905 "AUTH OAUTHBEARER command"); | |
906 return NGX_MAIL_PARSE_INVALID_COMMAND; | |
907 } | |
908 | |
909 /* | |
910 * RFC 7628 | |
911 * "n,a=user@example.com,^A...^Aauth=Bearer <token>^A^A" | |
912 */ | |
913 | |
914 p = oauth.data; | |
915 last = p + oauth.len; | |
916 | |
917 s->login.len = 0; | |
918 prev = NULL; | |
919 | |
920 while (p < last) { | |
921 if (*p == ',') { | |
922 if (prev | |
923 && (size_t) (p - prev) > sizeof("a=") - 1 | |
924 && ngx_strncasecmp(prev, (u_char *) "a=", sizeof("a=") - 1) | |
925 == 0) | |
926 { | |
927 s->login.len = p - prev - (sizeof("a=") - 1); | |
928 s->login.data = prev + sizeof("a=") - 1; | |
929 break; | |
930 } | |
931 | |
932 p++; | |
933 prev = p; | |
934 continue; | |
935 } | |
936 | |
937 if (*p == '\1') { | |
938 break; | |
939 } | |
940 | |
941 p++; | |
942 } | |
943 | |
944 if (s->login.len == 0) { | |
945 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
946 "client sent invalid login in AUTH OAUTHBEARER command"); | |
947 return NGX_MAIL_PARSE_INVALID_COMMAND; | |
948 } | |
949 | |
950 s->passwd.len = 0; | |
951 prev = NULL; | |
952 | |
953 while (p < last) { | |
954 if (*p == '\1') { | |
955 if (prev | |
956 && (size_t) (p - prev) > sizeof("auth=Bearer ") - 1 | |
957 && ngx_strncasecmp(prev, (u_char *) "auth=Bearer ", | |
958 sizeof("auth=Bearer ") - 1) | |
959 == 0) | |
960 { | |
961 s->passwd.len = p - prev - (sizeof("auth=Bearer ") - 1); | |
962 s->passwd.data = prev + sizeof("auth=Bearer ") - 1; | |
963 break; | |
964 } | |
965 | |
966 p++; | |
967 prev = p; | |
968 continue; | |
969 } | |
970 | |
971 p++; | |
972 } | |
973 | |
974 if (s->passwd.len == 0) { | |
975 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
976 "client sent invalid token in AUTH OAUTHBEARER command"); | |
977 return NGX_MAIL_PARSE_INVALID_COMMAND; | |
978 } | |
979 | |
980 /* decode =2C =3D in login */ | |
981 | |
982 p = s->login.data; | |
983 d = s->login.data; | |
984 last = s->login.data + s->login.len; | |
985 | |
986 while (p < last) { | |
987 if (*p == '=') { | |
988 | |
989 /* | |
990 * login is always followed by other data, | |
991 * so p[1] and p[2] can be checked directly | |
992 */ | |
993 | |
994 if (p[1] == '2' && (p[2] == 'C' || p[2] == 'c')) { | |
995 *d++ = ','; | |
996 | |
997 } else if (p[1] == '3' && (p[2] == 'D' || p[2] == 'd')) { | |
998 *d++ = '='; | |
999 | |
1000 } else { | |
1001 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
1002 "client sent invalid login in " | |
1003 "AUTH OAUTHBEARER command"); | |
1004 return NGX_MAIL_PARSE_INVALID_COMMAND; | |
1005 } | |
1006 | |
1007 p += 3; | |
1008 continue; | |
1009 } | |
1010 | |
1011 *d++ = *p++; | |
1012 } | |
1013 | |
1014 s->login.len = d - s->login.data; | |
1015 | |
1016 ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0, | |
1017 "mail auth oauthbearer: \"%V\" \"%V\"", | |
1018 &s->login, &s->passwd); | |
1019 | |
1020 s->auth_method = NGX_MAIL_AUTH_OAUTHBEARER; | |
1021 | |
1022 return NGX_DONE; | |
1023 } | |
1024 | |
1025 | |
758 void | 1026 void |
759 ngx_mail_send(ngx_event_t *wev) | 1027 ngx_mail_send(ngx_event_t *wev) |
760 { | 1028 { |
761 ngx_int_t n; | 1029 ngx_int_t n; |
762 ngx_connection_t *c; | 1030 ngx_connection_t *c; |
917 void | 1185 void |
918 ngx_mail_auth(ngx_mail_session_t *s, ngx_connection_t *c) | 1186 ngx_mail_auth(ngx_mail_session_t *s, ngx_connection_t *c) |
919 { | 1187 { |
920 s->args.nelts = 0; | 1188 s->args.nelts = 0; |
921 | 1189 |
922 if (s->buffer->pos == s->buffer->last) { | 1190 if (s->state) { |
923 s->buffer->pos = s->buffer->start; | 1191 /* preserve tag */ |
924 s->buffer->last = s->buffer->start; | 1192 s->arg_start = s->buffer->pos; |
925 } | 1193 |
926 | 1194 } else { |
927 s->state = 0; | 1195 if (s->buffer->pos == s->buffer->last) { |
1196 s->buffer->pos = s->buffer->start; | |
1197 s->buffer->last = s->buffer->start; | |
1198 } | |
1199 } | |
928 | 1200 |
929 if (c->read->timer_set) { | 1201 if (c->read->timer_set) { |
930 ngx_del_timer(c->read); | 1202 ngx_del_timer(c->read); |
931 } | 1203 } |
932 | 1204 |