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