diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:16:13 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:16:13 +0000 |
commit | e90fcc54809db2591dc083f43ef54c6ec8c60847 (patch) | |
tree | f20bc206c3c2d5d59d37c46c5cf5d53a20642556 /src/auths | |
parent | Initial commit. (diff) | |
download | exim4-e90fcc54809db2591dc083f43ef54c6ec8c60847.tar.xz exim4-e90fcc54809db2591dc083f43ef54c6ec8c60847.zip |
Adding upstream version 4.96.upstream/4.96upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
32 files changed, 7498 insertions, 0 deletions
diff --git a/src/auths/Makefile b/src/auths/Makefile new file mode 100644 index 0000000..e85b22a --- /dev/null +++ b/src/auths/Makefile @@ -0,0 +1,45 @@ +# Make file for building a library containing all the available authorization +# methods, and calling it auths.a. In addition, there are functions that are +# of general use in several methods; these are in separate modules so they are +# linked in only when needed. This Makefile is called from the main make file, +# after cd'ing to the auths subdirectory. When the relevant AUTH_ macros are +# defined, the equivalent modules herein is not included in the final binary. + +OBJ = auth-spa.o call_pam.o call_pwcheck.o \ + call_radius.o check_serv_cond.o cram_md5.o cyrus_sasl.o dovecot.o \ + external.o get_data.o get_no64_data.o gsasl_exim.o heimdal_gssapi.o \ + plaintext.o pwcheck.o \ + spa.o tls.o xtextdecode.o xtextencode.o + +auths.a: $(OBJ) + @$(RM_COMMAND) -f auths.a + @echo "$(AR) auths.a" + $(FE)$(AR) auths.a $(OBJ) + $(RANLIB) $@ + +.SUFFIXES: .o .c +.c.o:; @echo "$(CC) $*.c" + $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c + +auth-spa.o: $(HDRS) auth-spa.c +call_pam.o: $(HDRS) call_pam.c +call_pwcheck.o: $(HDRS) call_pwcheck.c pwcheck.h +call_radius.o: $(HDRS) call_radius.c +check_serv_cond.o: $(HDRS) check_serv_cond.c +get_data.o: $(HDRS) get_data.c +get_no64_data.o: $(HDRS) get_no64_data.c +pwcheck.o: $(HDRS) pwcheck.c pwcheck.h +xtextdecode.o: $(HDRS) xtextdecode.c +xtextencode.o: $(HDRS) xtextencode.c + +cram_md5.o: $(HDRS) cram_md5.c cram_md5.h +cyrus_sasl.o: $(HDRS) cyrus_sasl.c cyrus_sasl.h +dovecot.o: $(HDRS) dovecot.c dovecot.h +external.o: $(HDRS) external.c external.h +gsasl_exim.o: $(HDRS) gsasl_exim.c gsasl_exim.h +heimdal_gssapi.o: $(HDRS) heimdal_gssapi.c heimdal_gssapi.h +plaintext.o: $(HDRS) plaintext.c plaintext.h +spa.o: $(HDRS) spa.c spa.h +tls.o: $(HDRS) tls.c tls.h + +# End diff --git a/src/auths/README b/src/auths/README new file mode 100644 index 0000000..66bdcdc --- /dev/null +++ b/src/auths/README @@ -0,0 +1,98 @@ +AUTHS + +The modules in this directory are in support of various authentication +functions. Some of them, such as the base64 encoding/decoding and MD5 +computation, are just functions that might be used by several authentication +mechanisms. Others are the SMTP AUTH mechanisms themselves, included in the +final binary if the relevant AUTH_XXX value is set in Local/Makefile. The +general functions are in separate modules so that they get included in the +final binary only if they are actually called from somewhere. + +GENERAL FUNCTIONS + +The API for each of these functions is documented with the function's code. + + auth_b64encode encode in base 64 + auth_b64decode decode from base 64 + auth_call_pam do PAM authentication (if build with SUPPORT_PAM) + auth_get_data issue SMTP AUTH challenge and read response + auth_xtextencode encode as xtext + auth_xtextdecode decode from xtext + +INTERFACE TO SMTP AUTHENTICATION MECHANISMS + +These are general SASL mechanisms, adapted for use with SMTP. Each +authentication mechanism has three functions, for initialization, server +authentication, and client authentication. + +INITIALIZATION + +The initialization function is called when the configuration is read, and can +check for incomplete or illegal settings. It has one argument, a pointer to the +instance block for this configured mechanism. It must set the flags called +"server" and "client" in the generic auth_instance block to indicate whether +the server and/or client functions are available for this authenticator. +Typically this depends on whether server or client configuration options have +been set, but it is also possible to have an authenticator that has only one of +the server or client functions. The function may not touch big_buffer. + +SERVER AUTHENTICATION + +The second function performs authentication as a server. It receives a pointer +to the instance block, and its second argument is the remainder of the data +from the AUTH command. The numeric variable maximum setting (expand_nmax) is +set to zero, with $0 initialized as unset. The authenticator may set up numeric +variables according to its (old) specification and $auth<n> variables the +preferred ones nowadays; it should leave them set at the end so that they can +be used for the expansion of the generic server_set_id option, which happens +centrally. + +This function has access to the SMTP input and output so that it can write +intermediate responses and read more data if necessary. There is a packaged +function in auth_get_data() which outputs a challenge and reads a response. + +The yield of a server authentication check must be one of: + + OK success + DEFER couldn't complete the check + FAIL authentication failed + CANCELLED authentication forced to fail by "*" response to challenge, + or by certain forced string expansion failures + BAD64 bad base64 data received + UNEXPECTED unexpected data received + +In the case of DEFER, auth_defer_msg should point to an error message. + +CLIENT AUTHENTICATION + +The third function performs authentication as a client. It receives a pointer +to the instance block, and four further arguments: + + The smtp_context item for the connection to the remote host. + + The normal command-reading timeout value. + + A pointer to a buffer, to be used for receiving responses. It is done this + way so that the buffer is available for logging etc. in the calling + function in cases of error. + + The size of the buffer. + +The yield of a client authentication check must be one of: + + OK success + FAIL_SEND error after writing a command; errno is set + FAIL failed after reading a response; + either errno is set (for timeouts, I/O failures) or + the buffer contains the SMTP response line + CANCELLED the client cancelled authentication (often "fail" in expansion) + the buffer may contain a message; if not, *buffer = 0 + ERROR local problem (typically expansion error); message in buffer + +To communicate with the remote host the client should call +smtp_write_command(). If this yields FALSE, the authenticator should return +FAIL. After a successful write, the response is received by a call to +smtp_read_response(), which should use the buffer handed to the client function +as an argument. + +**** diff --git a/src/auths/auth-spa.c b/src/auths/auth-spa.c new file mode 100644 index 0000000..8d886b6 --- /dev/null +++ b/src/auths/auth-spa.c @@ -0,0 +1,1524 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* + * This file provides the necessary methods for authenticating with + * Microsoft's Secure Password Authentication. + + * All the original code used here was torn by Marc Prud'hommeaux out of the + * Samba project (by Andrew Tridgell, Jeremy Allison, and others). + * + * Copyright (c) The Exim Maintainers 2021 + + * Tom Kistner provided additional code, adding spa_build_auth_challenge() to + * support server authentication mode. + + * Mark Lyda provided a patch to solve this problem: + + - Exim is indicating in its Authentication Request message (Type 1) that it + can transmit text in either Unicode or OEM format. + + - Microsoft's SMTP server (smtp.email.msn.com) is responding in its + Challenge message (Type 2) that it will be expecting the OEM format. + + - Exim does not pay attention to the text format requested by Microsoft's + SMTP server and, instead, defaults to using the Unicode format. + + * References: + * http://www.innovation.ch/java/ntlm.html + * http://www.kuro5hin.org/story/2002/4/28/1436/66154 + + * It seems that some systems have existing but different definitions of some + * of the following types. I received a complaint about "int16" causing + * compilation problems. So I (PH) have renamed them all, to be on the safe + * side, by adding 'x' on the end. + + * typedef signed short int16; + * typedef unsigned short uint16; + * typedef unsigned uint32; + * typedef unsigned char uint8; + + * The API is extremely simple: + * 1. Form a SPA authentication request based on the username + * and (optional) domain + * 2. Send the request to the server and get an SPA challenge + * 3. Build the challenge response and send it back. + * + * Example usage is as + * follows: + * +int main (int argc, char ** argv) +{ + SPAAuthRequest request; + SPAAuthChallenge challenge; + SPAAuthResponse response; + char msgbuf[2048]; + char buffer[512]; + char *username, *password, *domain, *challenge_str; + + if (argc < 3) + { + printf ("Usage: %s <username> <password> [SPA Challenge]\n", + argv [0]); + exit (1); + } + + username = argv [1]; + password = argv [2]; + domain = 0; + + spa_build_auth_request (&request, username, domain); + + spa_bits_to_base64 (msgbuf, US &request, + spa_request_length(&request)); + + printf ("SPA Login request for username=%s:\n %s\n", + argv [1], msgbuf); + + if (argc < 4) + { + printf ("Run: %s <username> <password> [NTLM Challenge] " \ + "to complete authenitcation\n", argv [0]); + exit (0); + } + + challenge_str = argv [3]; + + if (spa_base64_to_bits (CS &challenge, sizeof(challenge), + CCS (challenge_str))<0) + { + printf("bad base64 data in challenge: %s\n", challenge_str); + exit (1); + } + + spa_build_auth_response (&challenge, &response, username, password); + spa_bits_to_base64 (msgbuf, US &response, + spa_request_length(&response)); + + printf ("SPA Response to challenge:\n %s\n for " \ + "username=%s, password=%s:\n %s\n", + argv[3], argv [1], argv [2], msgbuf); + return 0; +} + * + * + * All the client code used here was torn by Marc Prud'hommeaux out of the + * Samba project (by Andrew Tridgell, Jeremy Allison, and others). + * Previous comments are below: + */ + +/* + Unix SMB/Netbios implementation. + Version 1.9. + + a partial implementation of DES designed for use in the + SMB authentication protocol + + Copyright (C) Andrew Tridgell 1998 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + + +/* NOTES: + + This code makes no attempt to be fast! In fact, it is a very + slow implementation + + This code is NOT a complete DES implementation. It implements only + the minimum necessary for SMB authentication, as used by all SMB + products (including every copy of Microsoft Windows95 ever sold) + + In particular, it can only do a unchained forward DES pass. This + means it is not possible to use this code for encryption/decryption + of data, instead it is only useful as a "hash" algorithm. + + There is no entry point into this code that allows normal DES operation. + + I believe this means that this code does not come under ITAR + regulations but this is NOT a legal opinion. If you are concerned + about the applicability of ITAR regulations to this code then you + should confirm it for yourself (and maybe let me know if you come + up with a different answer to the one above) +*/ + +#define DEBUG_X(a,b) ; + +extern int DEBUGLEVEL; + +#include "../exim.h" +#include "auth-spa.h" +#include <assert.h> + + +#ifndef _BYTEORDER_H +# define _BYTEORDER_H + +# define RW_PCVAL(read,inbuf,outbuf,len) \ + { if (read) { PCVAL (inbuf,0,outbuf,len); } \ + else { PSCVAL(inbuf,0,outbuf,len); } } + +# define RW_PIVAL(read,big_endian,inbuf,outbuf,len) \ + { if (read) { if (big_endian) { RPIVAL(inbuf,0,outbuf,len); } else { PIVAL(inbuf,0,outbuf,len); } } \ + else { if (big_endian) { RPSIVAL(inbuf,0,outbuf,len); } else { PSIVAL(inbuf,0,outbuf,len); } } } + +# define RW_PSVAL(read,big_endian,inbuf,outbuf,len) \ + { if (read) { if (big_endian) { RPSVAL(inbuf,0,outbuf,len); } else { PSVAL(inbuf,0,outbuf,len); } } \ + else { if (big_endian) { RPSSVAL(inbuf,0,outbuf,len); } else { PSSVAL(inbuf,0,outbuf,len); } } } + +# define RW_CVAL(read, inbuf, outbuf, offset) \ + { if (read) { (outbuf) = CVAL (inbuf,offset); } \ + else { SCVAL(inbuf,offset,outbuf); } } + +# define RW_IVAL(read, big_endian, inbuf, outbuf, offset) \ + { if (read) { (outbuf) = ((big_endian) ? RIVAL(inbuf,offset) : IVAL (inbuf,offset)); } \ + else { if (big_endian) { RSIVAL(inbuf,offset,outbuf); } else { SIVAL(inbuf,offset,outbuf); } } } + +# define RW_SVAL(read, big_endian, inbuf, outbuf, offset) \ + { if (read) { (outbuf) = ((big_endian) ? RSVAL(inbuf,offset) : SVAL (inbuf,offset)); } \ + else { if (big_endian) { RSSVAL(inbuf,offset,outbuf); } else { SSVAL(inbuf,offset,outbuf); } } } + +# undef CAREFUL_ALIGNMENT + +/* we know that the 386 can handle misalignment and has the "right" + byteorder */ +# ifdef __i386__ +# define CAREFUL_ALIGNMENT 0 +# endif + +# ifndef CAREFUL_ALIGNMENT +# define CAREFUL_ALIGNMENT 1 +# endif + +# define CVAL(buf,pos) ((US (buf))[pos]) +# define PVAL(buf,pos) ((unsigned)CVAL(buf,pos)) +# define SCVAL(buf,pos,val) (CVAL(buf,pos) = (val)) + + +# if CAREFUL_ALIGNMENT + +# define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8) +# define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16) +# define SSVALX(buf,pos,val) (CVAL(buf,pos)=(val)&0xFF,CVAL(buf,pos+1)=(val)>>8) +# define SIVALX(buf,pos,val) (SSVALX(buf,pos,val&0xFFFF),SSVALX(buf,pos+2,val>>16)) +# define SVALS(buf,pos) ((int16x)SVAL(buf,pos)) +# define IVALS(buf,pos) ((int32x)IVAL(buf,pos)) +# define SSVAL(buf,pos,val) SSVALX((buf),(pos),((uint16x)(val))) +# define SIVAL(buf,pos,val) SIVALX((buf),(pos),((uint32x)(val))) +# define SSVALS(buf,pos,val) SSVALX((buf),(pos),((int16x)(val))) +# define SIVALS(buf,pos,val) SIVALX((buf),(pos),((int32x)(val))) + +# else /* CAREFUL_ALIGNMENT */ + +/* this handles things for architectures like the 386 that can handle + alignment errors */ +/* + WARNING: This section is dependent on the length of int16x and int32x + being correct +*/ + +/* get single value from an SMB buffer */ +# define SVAL(buf,pos) (*(uint16x *)(CS (buf) + (pos))) +# define IVAL(buf,pos) (*(uint32x *)(CS (buf) + (pos))) +# define SVALS(buf,pos) (*(int16x *)(CS (buf) + (pos))) +# define IVALS(buf,pos) (*(int32x *)(CS (buf) + (pos))) + +/* store single value in an SMB buffer */ +# define SSVAL(buf,pos,val) SVAL(buf,pos)=((uint16x)(val)) +# define SIVAL(buf,pos,val) IVAL(buf,pos)=((uint32x)(val)) +# define SSVALS(buf,pos,val) SVALS(buf,pos)=((int16x)(val)) +# define SIVALS(buf,pos,val) IVALS(buf,pos)=((int32x)(val)) + +# endif /* CAREFUL_ALIGNMENT */ + +/* macros for reading / writing arrays */ + +# define SMBMACRO(macro,buf,pos,val,len,size) \ +{ for (int l = 0; l < (len); l++) (val)[l] = macro((buf), (pos) + (size)*l); } + +# define SSMBMACRO(macro,buf,pos,val,len,size) \ +{ for (int l = 0; l < (len); l++) macro((buf), (pos) + (size)*l, (val)[l]); } + +/* reads multiple data from an SMB buffer */ +# define PCVAL(buf,pos,val,len) SMBMACRO(CVAL,buf,pos,val,len,1) +# define PSVAL(buf,pos,val,len) SMBMACRO(SVAL,buf,pos,val,len,2) +# define PIVAL(buf,pos,val,len) SMBMACRO(IVAL,buf,pos,val,len,4) +# define PCVALS(buf,pos,val,len) SMBMACRO(CVALS,buf,pos,val,len,1) +# define PSVALS(buf,pos,val,len) SMBMACRO(SVALS,buf,pos,val,len,2) +# define PIVALS(buf,pos,val,len) SMBMACRO(IVALS,buf,pos,val,len,4) + +/* stores multiple data in an SMB buffer */ +# define PSCVAL(buf,pos,val,len) SSMBMACRO(SCVAL,buf,pos,val,len,1) +# define PSSVAL(buf,pos,val,len) SSMBMACRO(SSVAL,buf,pos,val,len,2) +# define PSIVAL(buf,pos,val,len) SSMBMACRO(SIVAL,buf,pos,val,len,4) +# define PSCVALS(buf,pos,val,len) SSMBMACRO(SCVALS,buf,pos,val,len,1) +# define PSSVALS(buf,pos,val,len) SSMBMACRO(SSVALS,buf,pos,val,len,2) +# define PSIVALS(buf,pos,val,len) SSMBMACRO(SIVALS,buf,pos,val,len,4) + + +/* now the reverse routines - these are used in nmb packets (mostly) */ +# define SREV(x) ((((x)&0xFF)<<8) | (((x)>>8)&0xFF)) +# define IREV(x) ((SREV(x)<<16) | (SREV((x)>>16))) + +# define RSVAL(buf,pos) SREV(SVAL(buf,pos)) +# define RSVALS(buf,pos) SREV(SVALS(buf,pos)) +# define RIVAL(buf,pos) IREV(IVAL(buf,pos)) +# define RIVALS(buf,pos) IREV(IVALS(buf,pos)) +# define RSSVAL(buf,pos,val) SSVAL(buf,pos,SREV(val)) +# define RSSVALS(buf,pos,val) SSVALS(buf,pos,SREV(val)) +# define RSIVAL(buf,pos,val) SIVAL(buf,pos,IREV(val)) +# define RSIVALS(buf,pos,val) SIVALS(buf,pos,IREV(val)) + +/* reads multiple data from an SMB buffer (big-endian) */ +# define RPSVAL(buf,pos,val,len) SMBMACRO(RSVAL,buf,pos,val,len,2) +# define RPIVAL(buf,pos,val,len) SMBMACRO(RIVAL,buf,pos,val,len,4) +# define RPSVALS(buf,pos,val,len) SMBMACRO(RSVALS,buf,pos,val,len,2) +# define RPIVALS(buf,pos,val,len) SMBMACRO(RIVALS,buf,pos,val,len,4) + +/* stores multiple data in an SMB buffer (big-endian) */ +# define RPSSVAL(buf,pos,val,len) SSMBMACRO(RSSVAL,buf,pos,val,len,2) +# define RPSIVAL(buf,pos,val,len) SSMBMACRO(RSIVAL,buf,pos,val,len,4) +# define RPSSVALS(buf,pos,val,len) SSMBMACRO(RSSVALS,buf,pos,val,len,2) +# define RPSIVALS(buf,pos,val,len) SSMBMACRO(RSIVALS,buf,pos,val,len,4) + +# define DBG_RW_PCVAL(charmode,string,depth,base,read,inbuf,outbuf,len) \ + { RW_PCVAL(read,inbuf,outbuf,len) \ + DEBUG_X(5,("%s%04x %s: ", \ + tab_depth(depth), base,string)); \ + if (charmode) print_asc(5, US (outbuf), (len)); else \ + for (int idx = 0; idx < len; idx++) { DEBUG_X(5,("%02x ", (outbuf)[idx])); } \ + DEBUG_X(5,("\n")); } + +# define DBG_RW_PSVAL(charmode,string,depth,base,read,big_endian,inbuf,outbuf,len) \ + { RW_PSVAL(read,big_endian,inbuf,outbuf,len) \ + DEBUG_X(5,("%s%04x %s: ", \ + tab_depth(depth), base,string)); \ + if (charmode) print_asc(5, US (outbuf), 2*(len)); else \ + for (int idx = 0; idx < len; idx++) { DEBUG_X(5,("%04x ", (outbuf)[idx])); } \ + DEBUG_X(5,("\n")); } + +# define DBG_RW_PIVAL(charmode,string,depth,base,read,big_endian,inbuf,outbuf,len) \ + { RW_PIVAL(read,big_endian,inbuf,outbuf,len) \ + DEBUG_X(5,("%s%04x %s: ", \ + tab_depth(depth), base,string)); \ + if (charmode) print_asc(5, US (outbuf), 4*(len)); else \ + for (int idx = 0; idx < len; idx++) { DEBUG_X(5,("%08x ", (outbuf)[idx])); } \ + DEBUG_X(5,("\n")); } + +# define DBG_RW_CVAL(string,depth,base,read,inbuf,outbuf) \ + { RW_CVAL(read,inbuf,outbuf,0) \ + DEBUG_X(5,("%s%04x %s: %02x\n", \ + tab_depth(depth), base, string, outbuf)); } + +# define DBG_RW_SVAL(string,depth,base,read,big_endian,inbuf,outbuf) \ + { RW_SVAL(read,big_endian,inbuf,outbuf,0) \ + DEBUG_X(5,("%s%04x %s: %04x\n", \ + tab_depth(depth), base, string, outbuf)); } + +# define DBG_RW_IVAL(string,depth,base,read,big_endian,inbuf,outbuf) \ + { RW_IVAL(read,big_endian,inbuf,outbuf,0) \ + DEBUG_X(5,("%s%04x %s: %08x\n", \ + tab_depth(depth), base, string, outbuf)); } + +#endif /* _BYTEORDER_H */ + +void E_P16 (uschar *p14, uschar *p16); +void E_P24 (uschar *p21, uschar *c8, uschar *p24); +void D_P16 (uschar *p14, uschar *in, uschar *out); +void SMBOWFencrypt (uschar passwd[16], uschar * c8, uschar p24[24]); + +void mdfour (uschar *out, uschar *in, int n); + + +/* + * base64.c -- base-64 conversion routines. + * + * For license terms, see the file COPYING in this directory. + * + * This base 64 encoding is defined in RFC2045 section 6.8, + * "Base64 Content-Transfer-Encoding", but lines must not be broken in the + * scheme used here. + */ + +static const char base64digits[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +#define BAD (char) -1 +static const char base64val[] = { + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, + BAD, + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, + BAD, + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, 62, BAD, BAD, BAD, + 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, BAD, BAD, BAD, BAD, BAD, BAD, + BAD, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, BAD, BAD, BAD, BAD, BAD, + BAD, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, BAD, BAD, BAD, BAD, BAD +}; +#define DECODE64(c) (isascii(c) ? base64val[c] : BAD) + +void +spa_bits_to_base64 (uschar *out, const uschar *in, int inlen) +/* raw bytes in quasi-big-endian order to base 64 string (NUL-terminated) */ +{ +for (; inlen >= 3; inlen -= 3) + { + *out++ = base64digits[in[0] >> 2]; + *out++ = base64digits[((in[0] << 4) & 0x30) | (in[1] >> 4)]; + *out++ = base64digits[((in[1] << 2) & 0x3c) | (in[2] >> 6)]; + *out++ = base64digits[in[2] & 0x3f]; + in += 3; + } +if (inlen > 0) + { + uschar fragment; + + *out++ = base64digits[in[0] >> 2]; + fragment = (in[0] << 4) & 0x30; + if (inlen > 1) + fragment |= in[1] >> 4; + *out++ = base64digits[fragment]; + *out++ = (inlen < 2) ? '=' : base64digits[(in[1] << 2) & 0x3c]; + *out++ = '='; + } +*out = '\0'; +} + + +/* The outlength parameter was added by PH, December 2004 */ + +int +spa_base64_to_bits (char *out, int outlength, const char *in) +/* base 64 to raw bytes in quasi-big-endian order, returning count of bytes */ +{ +int len = 0; +uschar digit1, digit2, digit3, digit4; + +if (in[0] == '+' && in[1] == ' ') + in += 2; +if (*in == '\r') + return (0); + +do + { + if (len >= outlength) /* Added by PH */ + return -1; /* Added by PH */ + digit1 = in[0]; + if (DECODE64 (digit1) == BAD) + return -1; + digit2 = in[1]; + if (DECODE64 (digit2) == BAD) + return -1; + digit3 = in[2]; + if (digit3 != '=' && DECODE64 (digit3) == BAD) + return -1; + digit4 = in[3]; + if (digit4 != '=' && DECODE64 (digit4) == BAD) + return -1; + in += 4; + *out++ = (DECODE64 (digit1) << 2) | (DECODE64 (digit2) >> 4); + ++len; + if (digit3 != '=') + { + if (len >= outlength) /* Added by PH */ + return -1; /* Added by PH */ + *out++ = + ((DECODE64 (digit2) << 4) & 0xf0) | (DECODE64 (digit3) >> 2); + ++len; + if (digit4 != '=') + { + if (len >= outlength) /* Added by PH */ + return -1; /* Added by PH */ + *out++ = ((DECODE64 (digit3) << 6) & 0xc0) | DECODE64 (digit4); + ++len; + } + } + } +while (*in && *in != '\r' && digit4 != '='); + +return len; +} + + +static uschar perm1[56] = { 57, 49, 41, 33, 25, 17, 9, + 1, 58, 50, 42, 34, 26, 18, + 10, 2, 59, 51, 43, 35, 27, + 19, 11, 3, 60, 52, 44, 36, + 63, 55, 47, 39, 31, 23, 15, + 7, 62, 54, 46, 38, 30, 22, + 14, 6, 61, 53, 45, 37, 29, + 21, 13, 5, 28, 20, 12, 4 +}; + +static uschar perm2[48] = { 14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32 +}; + +static uschar perm3[64] = { 58, 50, 42, 34, 26, 18, 10, 2, + 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, + 64, 56, 48, 40, 32, 24, 16, 8, + 57, 49, 41, 33, 25, 17, 9, 1, + 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, + 63, 55, 47, 39, 31, 23, 15, 7 +}; + +static uschar perm4[48] = { 32, 1, 2, 3, 4, 5, + 4, 5, 6, 7, 8, 9, + 8, 9, 10, 11, 12, 13, + 12, 13, 14, 15, 16, 17, + 16, 17, 18, 19, 20, 21, + 20, 21, 22, 23, 24, 25, + 24, 25, 26, 27, 28, 29, + 28, 29, 30, 31, 32, 1 +}; + +static uschar perm5[32] = { 16, 7, 20, 21, + 29, 12, 28, 17, + 1, 15, 23, 26, + 5, 18, 31, 10, + 2, 8, 24, 14, + 32, 27, 3, 9, + 19, 13, 30, 6, + 22, 11, 4, 25 +}; + + +static uschar perm6[64] = { 40, 8, 48, 16, 56, 24, 64, 32, + 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, + 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, + 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, + 33, 1, 41, 9, 49, 17, 57, 25 +}; + + +static uschar sc[16] = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 }; + +static uschar sbox[8][4][16] = { + {{14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7}, + {0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8}, + {4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0}, + {15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}}, + + {{15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10}, + {3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5}, + {0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15}, + {13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}}, + + {{10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8}, + {13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1}, + {13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7}, + {1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}}, + + {{7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15}, + {13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9}, + {10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4}, + {3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}}, + + {{2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9}, + {14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6}, + {4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14}, + {11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}}, + + {{12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11}, + {10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8}, + {9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6}, + {4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}}, + + {{4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1}, + {13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6}, + {1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2}, + {6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}}, + + {{13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7}, + {1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2}, + {7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8}, + {2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}} +}; + +static void +permute (char *out, char *in, uschar * p, int n) +{ +for (int i = 0; i < n; i++) + out[i] = in[p[i] - 1]; +} + +static void +lshift (char *d, int count, int n) +{ +char out[64]; +for (int i = 0; i < n; i++) + out[i] = d[(i + count) % n]; +for (int i = 0; i < n; i++) + d[i] = out[i]; +} + +static void +concat (char *out, char *in1, char *in2, int l1, int l2) +{ +while (l1--) + *out++ = *in1++; +while (l2--) + *out++ = *in2++; +} + +static void +xor (char *out, char *in1, char *in2, int n) +{ +for (int i = 0; i < n; i++) + out[i] = in1[i] ^ in2[i]; +} + +static void +dohash (char *out, char *in, char *key, int forw) +{ +int i, j, k; +char pk1[56]; +char c[28]; +char d[28]; +char cd[56]; +char ki[16][48]; +char pd1[64]; +char l[32], r[32]; +char rl[64]; + +permute (pk1, key, perm1, 56); + +for (i = 0; i < 28; i++) + c[i] = pk1[i]; +for (i = 0; i < 28; i++) + d[i] = pk1[i + 28]; + +for (i = 0; i < 16; i++) + { + lshift (c, sc[i], 28); + lshift (d, sc[i], 28); + + concat (cd, c, d, 28, 28); + permute (ki[i], cd, perm2, 48); + } + +permute (pd1, in, perm3, 64); + +for (j = 0; j < 32; j++) + { + l[j] = pd1[j]; + r[j] = pd1[j + 32]; + } + +for (i = 0; i < 16; i++) + { + char er[48]; + char erk[48]; + char b[8][6]; + char cb[32]; + char pcb[32]; + char r2[32]; + + permute (er, r, perm4, 48); + + xor (erk, er, ki[forw ? i : 15 - i], 48); + + for (j = 0; j < 8; j++) + for (k = 0; k < 6; k++) + b[j][k] = erk[j * 6 + k]; + + for (j = 0; j < 8; j++) + { + int m, n; + m = (b[j][0] << 1) | b[j][5]; + + n = (b[j][1] << 3) | (b[j][2] << 2) | (b[j][3] << 1) | b[j][4]; + + for (k = 0; k < 4; k++) + b[j][k] = (sbox[j][m][n] & (1 << (3 - k))) ? 1 : 0; + } + + for (j = 0; j < 8; j++) + for (k = 0; k < 4; k++) + cb[j * 4 + k] = b[j][k]; + permute (pcb, cb, perm5, 32); + + xor (r2, l, pcb, 32); + + for (j = 0; j < 32; j++) + l[j] = r[j]; + + for (j = 0; j < 32; j++) + r[j] = r2[j]; + } + +concat (rl, r, l, 32, 32); + +permute (out, rl, perm6, 64); +} + +static void +str_to_key (uschar *str, uschar *key) +{ +int i; + +key[0] = str[0] >> 1; +key[1] = ((str[0] & 0x01) << 6) | (str[1] >> 2); +key[2] = ((str[1] & 0x03) << 5) | (str[2] >> 3); +key[3] = ((str[2] & 0x07) << 4) | (str[3] >> 4); +key[4] = ((str[3] & 0x0F) << 3) | (str[4] >> 5); +key[5] = ((str[4] & 0x1F) << 2) | (str[5] >> 6); +key[6] = ((str[5] & 0x3F) << 1) | (str[6] >> 7); +key[7] = str[6] & 0x7F; +for (i = 0; i < 8; i++) + key[i] = (key[i] << 1); +} + + +static void +smbhash (uschar *out, uschar *in, uschar *key, int forw) +{ +int i; +char outb[64]; +char inb[64]; +char keyb[64]; +uschar key2[8]; + +str_to_key (key, key2); + +for (i = 0; i < 64; i++) + { + inb[i] = (in[i / 8] & (1 << (7 - (i % 8)))) ? 1 : 0; + keyb[i] = (key2[i / 8] & (1 << (7 - (i % 8)))) ? 1 : 0; + outb[i] = 0; + } + +dohash (outb, inb, keyb, forw); + +for (i = 0; i < 8; i++) + out[i] = 0; + +for (i = 0; i < 64; i++) + if (outb[i]) + out[i / 8] |= (1 << (7 - (i % 8))); +} + +void +E_P16 (uschar *p14, uschar *p16) +{ +uschar sp8[8] = { 0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 }; +smbhash (p16, sp8, p14, 1); +smbhash (p16 + 8, sp8, p14 + 7, 1); +} + +void +E_P24 (uschar *p21, uschar *c8, uschar *p24) +{ +smbhash (p24, c8, p21, 1); +smbhash (p24 + 8, c8, p21 + 7, 1); +smbhash (p24 + 16, c8, p21 + 14, 1); +} + +void +D_P16 (uschar *p14, uschar *in, uschar *out) +{ +smbhash (out, in, p14, 0); +smbhash (out + 8, in + 8, p14 + 7, 0); +} + +/**************************************************************************** + Like strncpy but always null terminates. Make sure there is room! + The variable n should always be one less than the available size. +****************************************************************************/ + +char * +StrnCpy (char *dest, const char *src, size_t n) +{ +char *d = dest; +if (!dest) + return (NULL); +if (!src) + { + *dest = 0; + return (dest); + } +while (n-- && (*d++ = *src++)); +*d = 0; +return (dest); +} + +size_t +skip_multibyte_char (char c) +{ +/* bogus if to get rid of unused compiler warning */ +if (c) + return 0; +else + return 0; +} + + +/******************************************************************* +safe string copy into a known length string. maxlength does not +include the terminating zero. +********************************************************************/ + +char * +safe_strcpy (char *dest, const char *src, size_t maxlength) +{ +size_t len; + +if (!dest) + { + DEBUG_X (0, ("ERROR: NULL dest in safe_strcpy\n")); + return NULL; + } + +if (!src) + { + *dest = 0; + return dest; + } + +len = strlen (src); + +if (len > maxlength) + { + DEBUG_X (0, ("ERROR: string overflow by %d in safe_strcpy [%.50s]\n", + (int) (len - maxlength), src)); + len = maxlength; + } + +memcpy (dest, src, len); +dest[len] = 0; +return dest; +} + + +void +strupper (char *s) +{ +while (*s) + { + size_t skip = skip_multibyte_char (*s); + if (skip != 0) + s += skip; + else + { + if (islower ((uschar)(*s))) + *s = toupper (*s); + s++; + } + } +} + + +/* + This implements the X/Open SMB password encryption + It takes a password, a 8 byte "crypt key" and puts 24 bytes of + encrypted password into p24 + */ + +void +spa_smb_encrypt (uschar * passwd, uschar * c8, uschar * p24) +{ +uschar p14[15], p21[21]; + +memset (p21, '\0', 21); +memset (p14, '\0', 14); +StrnCpy (CS p14, CS passwd, 14); + +strupper (CS p14); +E_P16 (p14, p21); + +SMBOWFencrypt (p21, c8, p24); + +#ifdef DEBUG_PASSWORD +DEBUG_X (100, ("spa_smb_encrypt: lm#, challenge, response\n")); +dump_data (100, CS p21, 16); +dump_data (100, CS c8, 8); +dump_data (100, CS p24, 24); +#endif +} + +/* Routines for Windows NT MD4 Hash functions. */ +static int +_my_wcslen (int16x * str) +{ +int len = 0; +while (*str++ != 0) + len++; +return len; +} + +/* + * Convert a string into an NT UNICODE string. + * Note that regardless of processor type + * this must be in intel (little-endian) + * format. + */ + +static int +_my_mbstowcs (int16x * dst, uschar * src, int len) +{ +int i; +int16x val; + +for (i = 0; i < len; i++) + { + val = *src; + SSVAL (dst, 0, val); + dst++; + src++; + if (val == 0) + break; + } +return i; +} + +/* + * Creates the MD4 Hash of the users password in NT UNICODE. + */ + +void +E_md4hash (uschar * passwd, uschar * p16) +{ +int len; +int16x wpwd[129]; + +/* Password cannot be longer than 128 characters */ +len = strlen (CS passwd); +if (len > 128) + len = 128; +/* Password must be converted to NT unicode */ +_my_mbstowcs (wpwd, passwd, len); +wpwd[len] = 0; /* Ensure string is null terminated */ +/* Calculate length in bytes */ +len = _my_wcslen (wpwd) * sizeof (int16x); + +mdfour (p16, US wpwd, len); +} + +/* Does both the NT and LM owfs of a user's password */ +void +nt_lm_owf_gen (char *pwd, uschar nt_p16[16], uschar p16[16]) +{ +char passwd[130]; + +memset (passwd, '\0', 130); +safe_strcpy (passwd, pwd, sizeof (passwd) - 1); + +/* Calculate the MD4 hash (NT compatible) of the password */ +memset (nt_p16, '\0', 16); +E_md4hash (US passwd, nt_p16); + +#ifdef DEBUG_PASSWORD +DEBUG_X (100, ("nt_lm_owf_gen: pwd, nt#\n")); +dump_data (120, passwd, strlen (passwd)); +dump_data (100, CS nt_p16, 16); +#endif + +/* Mangle the passwords into Lanman format */ +passwd[14] = '\0'; +strupper (passwd); + +/* Calculate the SMB (lanman) hash functions of the password */ + +memset (p16, '\0', 16); +E_P16 (US passwd, US p16); + +#ifdef DEBUG_PASSWORD +DEBUG_X (100, ("nt_lm_owf_gen: pwd, lm#\n")); +dump_data (120, passwd, strlen (passwd)); +dump_data (100, CS p16, 16); +#endif +/* clear out local copy of user's password (just being paranoid). */ +memset (passwd, '\0', sizeof (passwd)); +} + +/* Does the des encryption from the NT or LM MD4 hash. */ +void +SMBOWFencrypt (uschar passwd[16], uschar * c8, uschar p24[24]) +{ +uschar p21[21]; + +memset (p21, '\0', 21); + +memcpy (p21, passwd, 16); +E_P24 (p21, c8, p24); +} + +/* Does the des encryption from the FIRST 8 BYTES of the NT or LM MD4 hash. */ +void +NTLMSSPOWFencrypt (uschar passwd[8], uschar * ntlmchalresp, uschar p24[24]) +{ +uschar p21[21]; + +memset (p21, '\0', 21); +memcpy (p21, passwd, 8); +memset (p21 + 8, 0xbd, 8); + +E_P24 (p21, ntlmchalresp, p24); +#ifdef DEBUG_PASSWORD +DEBUG_X (100, ("NTLMSSPOWFencrypt: p21, c8, p24\n")); +dump_data (100, CS p21, 21); +dump_data (100, CS ntlmchalresp, 8); +dump_data (100, CS p24, 24); +#endif +} + + +/* Does the NT MD4 hash then des encryption. */ + +void +spa_smb_nt_encrypt (uschar * passwd, uschar * c8, uschar * p24) +{ +uschar p21[21]; + +memset (p21, '\0', 21); + +E_md4hash (passwd, p21); +SMBOWFencrypt (p21, c8, p24); + +#ifdef DEBUG_PASSWORD +DEBUG_X (100, ("spa_smb_nt_encrypt: nt#, challenge, response\n")); +dump_data (100, CS p21, 16); +dump_data (100, CS c8, 8); +dump_data (100, CS p24, 24); +#endif +} + +static uint32x A, B, C, D; + +static uint32x +F (uint32x X, uint32x Y, uint32x Z) +{ +return (X & Y) | ((~X) & Z); +} + +static uint32x +G (uint32x X, uint32x Y, uint32x Z) +{ +return (X & Y) | (X & Z) | (Y & Z); +} + +static uint32x +H (uint32x X, uint32x Y, uint32x Z) +{ +return X ^ Y ^ Z; +} + +static uint32x +lshift_a (uint32x x, int s) +{ +x &= 0xFFFFFFFF; +return ((x << s) & 0xFFFFFFFF) | (x >> (32 - s)); +} + +#define ROUND1(a,b,c,d,k,s) a = lshift_a(a + F(b,c,d) + X[k], s) +#define ROUND2(a,b,c,d,k,s) a = lshift_a(a + G(b,c,d) + X[k] + (uint32x)0x5A827999,s) +#define ROUND3(a,b,c,d,k,s) a = lshift_a(a + H(b,c,d) + X[k] + (uint32x)0x6ED9EBA1,s) + +/* this applies md4 to 64 byte chunks */ +static void +spa_mdfour64 (uint32x * M) +{ +int j; +uint32x AA, BB, CC, DD; +uint32x X[16]; + +for (j = 0; j < 16; j++) + X[j] = M[j]; + +AA = A; +BB = B; +CC = C; +DD = D; + +ROUND1 (A, B, C, D, 0, 3); +ROUND1 (D, A, B, C, 1, 7); +ROUND1 (C, D, A, B, 2, 11); +ROUND1 (B, C, D, A, 3, 19); +ROUND1 (A, B, C, D, 4, 3); +ROUND1 (D, A, B, C, 5, 7); +ROUND1 (C, D, A, B, 6, 11); +ROUND1 (B, C, D, A, 7, 19); +ROUND1 (A, B, C, D, 8, 3); +ROUND1 (D, A, B, C, 9, 7); +ROUND1 (C, D, A, B, 10, 11); +ROUND1 (B, C, D, A, 11, 19); +ROUND1 (A, B, C, D, 12, 3); +ROUND1 (D, A, B, C, 13, 7); +ROUND1 (C, D, A, B, 14, 11); +ROUND1 (B, C, D, A, 15, 19); + +ROUND2 (A, B, C, D, 0, 3); +ROUND2 (D, A, B, C, 4, 5); +ROUND2 (C, D, A, B, 8, 9); +ROUND2 (B, C, D, A, 12, 13); +ROUND2 (A, B, C, D, 1, 3); +ROUND2 (D, A, B, C, 5, 5); +ROUND2 (C, D, A, B, 9, 9); +ROUND2 (B, C, D, A, 13, 13); +ROUND2 (A, B, C, D, 2, 3); +ROUND2 (D, A, B, C, 6, 5); +ROUND2 (C, D, A, B, 10, 9); +ROUND2 (B, C, D, A, 14, 13); +ROUND2 (A, B, C, D, 3, 3); +ROUND2 (D, A, B, C, 7, 5); +ROUND2 (C, D, A, B, 11, 9); +ROUND2 (B, C, D, A, 15, 13); + +ROUND3 (A, B, C, D, 0, 3); +ROUND3 (D, A, B, C, 8, 9); +ROUND3 (C, D, A, B, 4, 11); +ROUND3 (B, C, D, A, 12, 15); +ROUND3 (A, B, C, D, 2, 3); +ROUND3 (D, A, B, C, 10, 9); +ROUND3 (C, D, A, B, 6, 11); +ROUND3 (B, C, D, A, 14, 15); +ROUND3 (A, B, C, D, 1, 3); +ROUND3 (D, A, B, C, 9, 9); +ROUND3 (C, D, A, B, 5, 11); +ROUND3 (B, C, D, A, 13, 15); +ROUND3 (A, B, C, D, 3, 3); +ROUND3 (D, A, B, C, 11, 9); +ROUND3 (C, D, A, B, 7, 11); +ROUND3 (B, C, D, A, 15, 15); + +A += AA; +B += BB; +C += CC; +D += DD; + +A &= 0xFFFFFFFF; +B &= 0xFFFFFFFF; +C &= 0xFFFFFFFF; +D &= 0xFFFFFFFF; + +for (j = 0; j < 16; j++) + X[j] = 0; +} + +static void +copy64 (uint32x * M, uschar *in) +{ +int i; + +for (i = 0; i < 16; i++) + M[i] = (in[i * 4 + 3] << 24) | (in[i * 4 + 2] << 16) | + (in[i * 4 + 1] << 8) | (in[i * 4 + 0] << 0); +} + +static void +copy4 (uschar *out, uint32x x) +{ +out[0] = x & 0xFF; +out[1] = (x >> 8) & 0xFF; +out[2] = (x >> 16) & 0xFF; +out[3] = (x >> 24) & 0xFF; +} + +/* produce a md4 message digest from data of length n bytes */ +void +mdfour (uschar *out, uschar *in, int n) +{ +uschar buf[128]; +uint32x M[16]; +uint32x b = n * 8; +int i; + +A = 0x67452301; +B = 0xefcdab89; +C = 0x98badcfe; +D = 0x10325476; + +while (n > 64) + { + copy64 (M, in); + spa_mdfour64 (M); + in += 64; + n -= 64; + } + +for (i = 0; i < 128; i++) + buf[i] = 0; +memcpy (buf, in, n); +buf[n] = 0x80; + +if (n <= 55) + { + copy4 (buf + 56, b); + copy64 (M, buf); + spa_mdfour64 (M); + } +else + { + copy4 (buf + 120, b); + copy64 (M, buf); + spa_mdfour64 (M); + copy64 (M, buf + 64); + spa_mdfour64 (M); + } + +for (i = 0; i < 128; i++) + buf[i] = 0; +copy64 (M, buf); + +copy4 (out, A); +copy4 (out + 4, B); +copy4 (out + 8, C); +copy4 (out + 12, D); + +A = B = C = D = 0; +} + +char versionString[] = "libntlm version 0.21"; + +/* Utility routines that handle NTLM auth structures. */ + +/* The [IS]VAL macros are to take care of byte order for non-Intel + * Machines -- I think this file is OK, but it hasn't been tested. + * The other files (the ones stolen from Samba) should be OK. + */ + + +/* I am not crazy about these macros -- they seem to have gotten + * a bit complex. A new scheme for handling string/buffer fields + * in the structures probably needs to be designed + */ + +#define spa_bytes_add(ptr, header, buf, count) \ +{ \ +if (buf && (count) != 0) /* we hate -Wint-in-bool-contex */ \ + { \ + SSVAL(&ptr->header.len,0,count); \ + SSVAL(&ptr->header.maxlen,0,count); \ + SIVAL(&ptr->header.offset,0,((ptr->buffer - ((uint8x*)ptr)) + ptr->bufIndex)); \ + memcpy(ptr->buffer+ptr->bufIndex, buf, count); \ + ptr->bufIndex += count; \ + } \ +else \ + { \ + ptr->header.len = \ + ptr->header.maxlen = 0; \ + SIVAL(&ptr->header.offset,0,((ptr->buffer - ((uint8x*)ptr)) + ptr->bufIndex)); \ + } \ +} + +#define spa_string_add(ptr, header, string) \ +{ \ +char *p = string; \ +int len = 0; \ +if (p) len = strlen(p); \ +spa_bytes_add(ptr, header, (US p), len); \ +} + +#define spa_unicode_add_string(ptr, header, string) \ +{ \ +char *p = string; \ +uschar *b = NULL; \ +int len = 0; \ +if (p) \ + { \ + len = strlen(p); \ + b = strToUnicode(p); \ + } \ +spa_bytes_add(ptr, header, b, len*2); \ +} + + +#define GetUnicodeString(structPtr, header) \ +unicodeToString(((char*)structPtr) + IVAL(&structPtr->header.offset,0) , SVAL(&structPtr->header.len,0)/2) +#define GetString(structPtr, header) \ +toString(((CS structPtr) + IVAL(&structPtr->header.offset,0)), SVAL(&structPtr->header.len,0)) + +#ifdef notdef + +#define DumpBuffer(fp, structPtr, header) \ +dumpRaw(fp,(US structPtr)+IVAL(&structPtr->header.offset,0),SVAL(&structPtr->header.len,0)) + + +static void +dumpRaw (FILE * fp, uschar *buf, size_t len) +{ +int i; + +for (i = 0; i < len; ++i) + fprintf (fp, "%02x ", buf[i]); + +fprintf (fp, "\n"); +} + +#endif + +char * +unicodeToString (char *p, size_t len) +{ +int i; +static char buf[1024]; + +assert (len + 1 < sizeof buf); + +for (i = 0; i < len; ++i) + { + buf[i] = *p & 0x7f; + p += 2; + } + +buf[i] = '\0'; +return buf; +} + +static uschar * +strToUnicode (char *p) +{ +static uschar buf[1024]; +size_t l = strlen (p); +int i = 0; + +assert (l * 2 < sizeof buf); + +while (l--) + { + buf[i++] = *p++; + buf[i++] = 0; + } + +return buf; +} + +static uschar * +toString (char *p, size_t len) +{ +static uschar buf[1024]; + +assert (len + 1 < sizeof buf); + +memcpy (buf, p, len); +buf[len] = 0; +return buf; +} + +#ifdef notdef + +void +dumpSmbNtlmAuthRequest (FILE * fp, SPAAuthRequest * request) +{ +fprintf (fp, "NTLM Request:\n"); +fprintf (fp, " Ident = %s\n", request->ident); +fprintf (fp, " mType = %d\n", IVAL (&request->msgType, 0)); +fprintf (fp, " Flags = %08x\n", IVAL (&request->flags, 0)); +fprintf (fp, " User = %s\n", GetString (request, user)); +fprintf (fp, " Domain = %s\n", GetString (request, domain)); +} + +void +dumpSmbNtlmAuthChallenge (FILE * fp, SPAAuthChallenge * challenge) +{ +fprintf (fp, "NTLM Challenge:\n"); +fprintf (fp, " Ident = %s\n", challenge->ident); +fprintf (fp, " mType = %d\n", IVAL (&challenge->msgType, 0)); +fprintf (fp, " Domain = %s\n", GetUnicodeString (challenge, uDomain)); +fprintf (fp, " Flags = %08x\n", IVAL (&challenge->flags, 0)); +fprintf (fp, " Challenge = "); +dumpRaw (fp, challenge->challengeData, 8); +} + +void +dumpSmbNtlmAuthResponse (FILE * fp, SPAAuthResponse * response) +{ +fprintf (fp, "NTLM Response:\n"); +fprintf (fp, " Ident = %s\n", response->ident); +fprintf (fp, " mType = %d\n", IVAL (&response->msgType, 0)); +fprintf (fp, " LmResp = "); +DumpBuffer (fp, response, lmResponse); +fprintf (fp, " NTResp = "); +DumpBuffer (fp, response, ntResponse); +fprintf (fp, " Domain = %s\n", GetUnicodeString (response, uDomain)); +fprintf (fp, " User = %s\n", GetUnicodeString (response, uUser)); +fprintf (fp, " Wks = %s\n", GetUnicodeString (response, uWks)); +fprintf (fp, " sKey = "); +DumpBuffer (fp, response, sessionKey); +fprintf (fp, " Flags = %08x\n", IVAL (&response->flags, 0)); +} +#endif + +void +spa_build_auth_request (SPAAuthRequest * request, char *user, char *domain) +{ +char *u = strdup (user); +char *p = strchr (u, '@'); + +if (p) + { + if (!domain) + domain = p + 1; + *p = '\0'; + } + +request->bufIndex = 0; +memcpy (request->ident, "NTLMSSP\0\0\0", 8); +SIVAL (&request->msgType, 0, 1); +SIVAL (&request->flags, 0, 0x0000b207); /* have to figure out what these mean */ +spa_string_add (request, user, u); +spa_string_add (request, domain, domain); +free (u); +} + + + +void +spa_build_auth_challenge (SPAAuthRequest * request, SPAAuthChallenge * challenge) +{ +char chalstr[8]; +int i; +int p = (int)getpid(); +int random_seed = (int)time(NULL) ^ ((p << 16) | p); + +/* Ensure challenge data is cleared, in case it isn't all used. This +patch added by PH on suggestion of Russell King */ + +memset(challenge, 0, sizeof(SPAAuthChallenge)); + +challenge->bufIndex = 0; +memcpy (challenge->ident, "NTLMSSP\0", 8); +SIVAL (&challenge->msgType, 0, 2); +SIVAL (&challenge->flags, 0, 0x00008201); +SIVAL (&challenge->uDomain.len, 0, 0x0000); +SIVAL (&challenge->uDomain.maxlen, 0, 0x0000); +SIVAL (&challenge->uDomain.offset, 0, 0x00002800); + +/* generate eight pseudo random bytes (method ripped from host.c) */ + +for(i=0;i<8;i++) + { + chalstr[i] = (uschar)(random_seed >> 16) % 256; + random_seed = (1103515245 - (chalstr[i])) * random_seed + 12345; + } + +memcpy(challenge->challengeData,chalstr,8); +} + + + + +/* This is the original source of this function, preserved here for reference. +The new version below was re-organized by PH following a patch and some further +suggestions from Mark Lyda to fix the problem that is described at the head of +this module. At the same time, I removed the untidiness in the code below that +involves the "d" and "domain" variables. */ + +#ifdef NEVER +void +spa_build_auth_response (SPAAuthChallenge * challenge, + SPAAuthResponse * response, char *user, + char *password) +{ +uint8x lmRespData[24]; +uint8x ntRespData[24]; +char *d = strdup (GetUnicodeString (challenge, uDomain)); +char *domain = d; +char *u = strdup (user); +char *p = strchr (u, '@'); + +if (p) + { + domain = p + 1; + *p = '\0'; + } + +spa_smb_encrypt (US password, challenge->challengeData, lmRespData); +spa_smb_nt_encrypt (US password, challenge->challengeData, ntRespData); + +response->bufIndex = 0; +memcpy (response->ident, "NTLMSSP\0\0\0", 8); +SIVAL (&response->msgType, 0, 3); + +spa_bytes_add (response, lmResponse, lmRespData, 24); +spa_bytes_add (response, ntResponse, ntRespData, 24); +spa_unicode_add_string (response, uDomain, domain); +spa_unicode_add_string (response, uUser, u); +spa_unicode_add_string (response, uWks, u); +spa_string_add (response, sessionKey, NULL); + +response->flags = challenge->flags; + +free (d); +free (u); +} +#endif + + +/* This is the re-organized version (see comments above) */ + +void +spa_build_auth_response (SPAAuthChallenge * challenge, + SPAAuthResponse * response, char *user, + char *password) +{ +uint8x lmRespData[24]; +uint8x ntRespData[24]; +uint32x cf = IVAL(&challenge->flags, 0); +char *u = strdup (user); +char *p = strchr (u, '@'); +char *d = NULL; +char *domain; + +if (p) + { + domain = p + 1; + *p = '\0'; + } + +else domain = d = strdup((cf & 0x1)? + CCS GetUnicodeString(challenge, uDomain) : + CCS GetString(challenge, uDomain)); + +spa_smb_encrypt (US password, challenge->challengeData, lmRespData); +spa_smb_nt_encrypt (US password, challenge->challengeData, ntRespData); + +response->bufIndex = 0; +memcpy (response->ident, "NTLMSSP\0\0\0", 8); +SIVAL (&response->msgType, 0, 3); + +spa_bytes_add (response, lmResponse, lmRespData, (cf & 0x200) ? 24 : 0); +spa_bytes_add (response, ntResponse, ntRespData, (cf & 0x8000) ? 24 : 0); + +if (cf & 0x1) { /* Unicode Text */ + spa_unicode_add_string (response, uDomain, domain); + spa_unicode_add_string (response, uUser, u); + spa_unicode_add_string (response, uWks, u); +} else { /* OEM Text */ + spa_string_add (response, uDomain, domain); + spa_string_add (response, uUser, u); + spa_string_add (response, uWks, u); +} + +spa_string_add (response, sessionKey, NULL); +response->flags = challenge->flags; + +if (d != NULL) free (d); +free (u); +} diff --git a/src/auths/auth-spa.h b/src/auths/auth-spa.h new file mode 100644 index 0000000..cfe1b08 --- /dev/null +++ b/src/auths/auth-spa.h @@ -0,0 +1,92 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* + * This file provides the necessary methods for authenticating with + * Microsoft's Secure Password Authentication. + + * All the code used here was torn by Marc Prud'hommeaux out of the + * Samba project (by Andrew Tridgell, Jeremy Allison, and others). + */ + +/* December 2004: The spa_base64_to_bits() function has no length checking in +it. I have added a check. PH */ + +/* It seems that some systems have existing but different definitions of some +of the following types. I received a complaint about "int16" causing +compilation problems. So I (PH) have renamed them all, to be on the safe side. + +typedef signed short int16; +typedef unsigned short uint16; +typedef unsigned uint32; +typedef unsigned char uint8; +*/ + +typedef signed short int16x; +typedef unsigned short uint16x; +typedef unsigned uint32x; +typedef unsigned char uint8x; + +typedef struct +{ + uint16x len; + uint16x maxlen; + uint32x offset; +} SPAStrHeader; + +typedef struct +{ + char ident[8]; + uint32x msgType; + SPAStrHeader uDomain; + uint32x flags; + uint8x challengeData[8]; + uint8x reserved[8]; + SPAStrHeader emptyString; + uint8x buffer[1024]; + uint32x bufIndex; +} SPAAuthChallenge; + + +typedef struct +{ + char ident[8]; + uint32x msgType; + uint32x flags; + SPAStrHeader user; + SPAStrHeader domain; + uint8x buffer[1024]; + uint32x bufIndex; +} SPAAuthRequest; + +typedef struct +{ + char ident[8]; + uint32x msgType; + SPAStrHeader lmResponse; + SPAStrHeader ntResponse; + SPAStrHeader uDomain; + SPAStrHeader uUser; + SPAStrHeader uWks; + SPAStrHeader sessionKey; + uint32x flags; + uint8x buffer[1024]; + uint32x bufIndex; +} SPAAuthResponse; + +#define spa_request_length(ptr) (((ptr)->buffer - (uint8x*)(ptr)) + (ptr)->bufIndex) + +void spa_bits_to_base64 (unsigned char *, const unsigned char *, int); +int spa_base64_to_bits(char *, int, const char *); +void spa_build_auth_response (SPAAuthChallenge *challenge, + SPAAuthResponse *response, char *user, char *password); +void spa_build_auth_request (SPAAuthRequest *request, char *user, + char *domain); +extern void spa_smb_encrypt (unsigned char * passwd, unsigned char * c8, + unsigned char * p24); +extern void spa_smb_nt_encrypt (unsigned char * passwd, unsigned char * c8, + unsigned char * p24); +extern char *unicodeToString(char *p, size_t len); +extern void spa_build_auth_challenge(SPAAuthRequest *, SPAAuthChallenge *); + diff --git a/src/auths/call_pam.c b/src/auths/call_pam.c new file mode 100644 index 0000000..80f80f1 --- /dev/null +++ b/src/auths/call_pam.c @@ -0,0 +1,204 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* Copyright (c) The Exim Maintainers 2020 - 2021 */ +/* See the file NOTICE for conditions of use and distribution. */ + +#include "../exim.h" + +/* This module contains functions that call the PAM authentication mechanism +defined by Sun for Solaris and also available for Linux and other OS. + +We can't just compile this code and allow the library mechanism to omit the +functions if they are not wanted, because we need to have the PAM headers +available for compiling. Therefore, compile these functions only if SUPPORT_PAM +is defined. However, some compilers don't like compiling empty modules, so keep +them happy with a dummy when skipping the rest. Make it reference itself to +stop picky compilers complaining that it is unused, and put in a dummy argument +to stop even pickier compilers complaining about infinite loops. +Then use a mutually-recursive pair as gcc is just getting stupid. */ + +#ifndef SUPPORT_PAM +static void dummy(int x); +static void dummy2(int x) { dummy(x-1); } +static void dummy(int x) { dummy2(x-1); } +#else /* SUPPORT_PAM */ + +#ifdef PAM_H_IN_PAM +#include <pam/pam_appl.h> +#else +#include <security/pam_appl.h> +#endif + +/* According to the specification, it should be possible to have an application +data pointer passed to the conversation function. However, I was unable to get +this to work on Solaris 2.6, so static variables are used instead. */ + +static int pam_conv_had_error; +static const uschar *pam_args; +static BOOL pam_arg_ended; + + + +/************************************************* +* PAM conversation function * +*************************************************/ + +/* This function is passed to the PAM authentication function, and it calls it +back when it wants data from the client. The string list is in pam_args. When +we reach the end, we pass back an empty string once. If this function is called +again, it will give an error response. This is protection against something +crazy happening. + +Arguments: + num_msg number of messages associated with the call + msg points to an array of length num_msg of pam_message structures + resp set to point to the response block, which has to be got by + this function + appdata_ptr the application data pointer - not used because in Solaris + 2.6 it always arrived in pam_converse() as NULL + +Returns: a PAM return code +*/ + +static int +pam_converse (int num_msg, PAM_CONVERSE_ARG2_TYPE **msg, + struct pam_response **resp, void *appdata_ptr) +{ +int sep = 0; +struct pam_response *reply; + +/* It seems that PAM frees reply[] */ + +if ( pam_arg_ended + || !(reply = malloc(sizeof(struct pam_response) * num_msg))) + return PAM_CONV_ERR; + +for (int i = 0; i < num_msg; i++) + { + uschar *arg; + switch (msg[i]->msg_style) + { + case PAM_PROMPT_ECHO_ON: + case PAM_PROMPT_ECHO_OFF: + if (!(arg = string_nextinlist(&pam_args, &sep, NULL, 0))) + { + arg = US""; + pam_arg_ended = TRUE; + } + reply[i].resp = strdup(CCS arg); /* Use libc malloc, PAM frees resp directly*/ + reply[i].resp_retcode = PAM_SUCCESS; + break; + + case PAM_TEXT_INFO: /* Just acknowledge messages */ + case PAM_ERROR_MSG: + reply[i].resp_retcode = PAM_SUCCESS; + reply[i].resp = NULL; + break; + + default: /* Must be an error of some sort... */ + free(reply); + pam_conv_had_error = TRUE; + return PAM_CONV_ERR; + } + } + +*resp = reply; +return PAM_SUCCESS; +} + + + +/************************************************* +* Perform PAM authentication * +*************************************************/ + +/* This function calls the PAM authentication mechanism, passing over one or +more data strings. + +Arguments: + s a colon-separated list of strings + errptr where to point an error message + +Returns: OK if authentication succeeded + FAIL if authentication failed + ERROR some other error condition +*/ + +int +auth_call_pam(const uschar *s, uschar **errptr) +{ +pam_handle_t *pamh = NULL; +struct pam_conv pamc; +int pam_error; +int sep = 0; +uschar *user; + +/* Set up the input data structure: the address of the conversation function, +and a pointer to application data, which we don't use because I couldn't get it +to work under Solaris 2.6 - it always arrived in pam_converse() as NULL. */ + +pamc.conv = pam_converse; +pamc.appdata_ptr = NULL; + +/* Initialize the static data - the current input data, the error flag, and the +flag for data end. */ + +pam_args = s; +pam_conv_had_error = FALSE; +pam_arg_ended = FALSE; + +/* The first string in the list is the user. If this is an empty string, we +fail. PAM doesn't support authentication with an empty user (it prompts for it, +causing a potential mis-interpretation). */ + +user = string_nextinlist(&pam_args, &sep, NULL, 0); +if (user == NULL || user[0] == 0) return FAIL; + +/* Start off PAM interaction */ + +DEBUG(D_auth) + debug_printf("Running PAM authentication for user \"%s\"\n", user); + +pam_error = pam_start ("exim", CS user, &pamc, &pamh); + +/* Do the authentication - the pam_authenticate() will call pam_converse() to +get the data it wants. After successful authentication we call pam_acct_mgmt() +to apply any other restrictions (e.g. only some times of day). */ + +if (pam_error == PAM_SUCCESS) + { + pam_error = pam_authenticate (pamh, PAM_SILENT); + if (pam_error == PAM_SUCCESS && !pam_conv_had_error) + pam_error = pam_acct_mgmt (pamh, PAM_SILENT); + } + +/* Finish the PAM interaction - this causes it to clean up store etc. Unclear +what should be passed as the second argument. */ + +pam_end(pamh, PAM_SUCCESS); + +/* Sort out the return code. If not success, set the error message. */ + +if (pam_error == PAM_SUCCESS) + { + DEBUG(D_auth) debug_printf("PAM success\n"); + return OK; + } + +*errptr = US pam_strerror(pamh, pam_error); +DEBUG(D_auth) debug_printf("PAM error: %s\n", *errptr); + +if (pam_error == PAM_USER_UNKNOWN || + pam_error == PAM_AUTH_ERR || + pam_error == PAM_ACCT_EXPIRED) + return FAIL; + +return ERROR; +} + +#endif /* SUPPORT_PAM */ + +/* End of call_pam.c */ diff --git a/src/auths/call_pwcheck.c b/src/auths/call_pwcheck.c new file mode 100644 index 0000000..0adde44 --- /dev/null +++ b/src/auths/call_pwcheck.c @@ -0,0 +1,121 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2015 */ +/* Copyright (c) The Exim Maintainers 2020 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* This module contains interface functions to the two Cyrus authentication +daemons. The original one was "pwcheck", which gives its name to the source +file. This is now deprecated in favour of "saslauthd". */ + + +#include "../exim.h" +#include "pwcheck.h" + + +/************************************************* +* External entry point for pwcheck * +*************************************************/ + +/* This function calls the now-deprecated "pwcheck" Cyrus-SASL authentication +daemon, passing over a colon-separated user name and password. As this is +called from the string expander, the string will always be in dynamic store and +can be overwritten. + +Arguments: + s a colon-separated username:password string + errptr where to point an error message + +Returns: OK if authentication succeeded + FAIL if authentication failed + ERROR some other error condition +*/ + +int +auth_call_pwcheck(uschar *s, uschar **errptr) +{ +uschar *reply = NULL; +uschar *pw = Ustrrchr(s, ':'); + +if (pw == NULL) + { + *errptr = US"pwcheck: malformed input - missing colon"; + return ERROR; + } + +*pw++ = 0; /* Separate user and password */ + +DEBUG(D_auth) + debug_printf("Running pwcheck authentication for user \"%s\"\n", s); + +switch (pwcheck_verify_password(CS s, CS pw, CCSS &reply)) + { + case PWCHECK_OK: + DEBUG(D_auth) debug_printf("pwcheck: success (%s)\n", reply); + return OK; + + case PWCHECK_NO: + DEBUG(D_auth) debug_printf("pwcheck: access denied (%s)\n", reply); + return FAIL; + + default: + DEBUG(D_auth) debug_printf("pwcheck: query failed (%s)\n", reply); + *errptr = reply; + return ERROR; + } +} + + +/************************************************* +* External entry point for pwauthd * +*************************************************/ + +/* This function calls the "saslauthd" Cyrus-SASL authentication daemon, +saslauthd, As this is called from the string expander, all the strings will +always be in dynamic store and can be overwritten. + +Arguments: + username username + password password + service optional service + realm optional realm + errptr where to point an error message + +Returns: OK if authentication succeeded + FAIL if authentication failed + ERROR some other error condition +*/ + +int +auth_call_saslauthd(const uschar *username, const uschar *password, + const uschar *service, const uschar *realm, uschar **errptr) +{ +uschar *reply = NULL; + +if (service == NULL) service = US""; +if (realm == NULL) realm = US""; + +DEBUG(D_auth) + debug_printf("Running saslauthd authentication for user \"%s\" \n", username); + +switch (saslauthd_verify_password(username, password, service, + realm, (const uschar **)(&reply))) + { + case PWCHECK_OK: + DEBUG(D_auth) debug_printf("saslauthd: success (%s)\n", reply); + return OK; + + case PWCHECK_NO: + DEBUG(D_auth) debug_printf("saslauthd: access denied (%s)\n", reply); + return FAIL; + + default: + DEBUG(D_auth) debug_printf("saslauthd: query failed (%s)\n", reply); + *errptr = reply; + return ERROR; + } +} + +/* End of call_pwcheck.c */ diff --git a/src/auths/call_radius.c b/src/auths/call_radius.c new file mode 100644 index 0000000..e7f9f52 --- /dev/null +++ b/src/auths/call_radius.c @@ -0,0 +1,223 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2020 - 2022 */ +/* Copyright (c) University of Cambridge 1995 - 2016 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* This file was originally supplied by Ian Kirk. The libradius support came +from Alex Kiernan. */ + +#include "../exim.h" + +/* This module contains functions that call the Radius authentication +mechanism. + +We can't just compile this code and allow the library mechanism to omit the +functions if they are not wanted, because we need to have the Radius headers +available for compiling. Therefore, compile these functions only if +RADIUS_CONFIG_FILE is defined. However, some compilers don't like compiling +empty modules, so keep them happy with a dummy when skipping the rest. Make it +reference itself to stop picky compilers complaining that it is unused, and put +in a dummy argument to stop even pickier compilers complaining about infinite +loops. Then use a mutually-recursive pair as gcc is just getting stupid. */ + +#ifndef RADIUS_CONFIG_FILE +static void dummy(int x); +static void dummy2(int x) { dummy(x-1); } +static void dummy(int x) { dummy2(x-1); } +#else /* RADIUS_CONFIG_FILE */ + + +/* Two different Radius libraries are supported. The default is radiusclient, +using its original API. At release 0.4.0 the API changed. */ + +#ifdef RADIUS_LIB_RADLIB +# include <radlib.h> +#else +# if !defined(RADIUS_LIB_RADIUSCLIENT) && !defined(RADIUS_LIB_RADIUSCLIENTNEW) +# define RADIUS_LIB_RADIUSCLIENT +# endif + +# ifdef RADIUS_LIB_RADIUSCLIENTNEW +# define ENV FREERADIUSCLIENT_ENV /* Avoid clash with Berkeley DB */ +# include <freeradius-client.h> +# else +# include <radiusclient.h> +# endif +#endif + + + +/************************************************* +* Perform RADIUS authentication * +*************************************************/ + +/* This function calls the Radius authentication mechanism, passing over one or +more data strings. + +Arguments: + s a colon-separated list of strings + errptr where to point an error message + +Returns: OK if authentication succeeded + FAIL if authentication failed + ERROR some other error condition +*/ + +int +auth_call_radius(const uschar *s, uschar **errptr) +{ +uschar *user; +const uschar *radius_args = s; +int result; +int sep = 0; + +#ifdef RADIUS_LIB_RADLIB + struct rad_handle *h; +#else + #ifdef RADIUS_LIB_RADIUSCLIENTNEW + rc_handle *h; + #endif + VALUE_PAIR *send = NULL; + VALUE_PAIR *received; + unsigned int service = PW_AUTHENTICATE_ONLY; + char msg[4096]; +#endif + + +if (!(user = string_nextinlist(&radius_args, &sep, NULL, 0))) user = US""; + +DEBUG(D_auth) debug_printf("Running RADIUS authentication for user \"%s\" " + "and \"%s\"\n", user, radius_args); + +*errptr = NULL; + + +/* Authenticate using the radiusclient library */ + +#ifndef RADIUS_LIB_RADLIB + +rc_openlog("exim"); + +#ifdef RADIUS_LIB_RADIUSCLIENT +if (rc_read_config(RADIUS_CONFIG_FILE) != 0) + *errptr = string_sprintf("RADIUS: can't open %s", RADIUS_CONFIG_FILE); + +else if (rc_read_dictionary(rc_conf_str("dictionary")) != 0) + *errptr = US"RADIUS: can't read dictionary"; + +else if (!rc_avpair_add(&send, PW_USER_NAME, user, 0)) + *errptr = US"RADIUS: add user name failed"; + +else if (!rc_avpair_add(&send, PW_USER_PASSWORD, CS radius_args, 0)) + *errptr = US"RADIUS: add password failed"); + +else if (!rc_avpair_add(&send, PW_SERVICE_TYPE, &service, 0)) + *errptr = US"RADIUS: add service type failed"; + +#else /* RADIUS_LIB_RADIUSCLIENT unset => RADIUS_LIB_RADIUSCLIENT2 */ + +if (!(h = rc_read_config(RADIUS_CONFIG_FILE))) + *errptr = string_sprintf("RADIUS: can't open %s", RADIUS_CONFIG_FILE); + +else if (rc_read_dictionary(h, rc_conf_str(h, "dictionary")) != 0) + *errptr = US"RADIUS: can't read dictionary"; + +else if (!rc_avpair_add(h, &send, PW_USER_NAME, user, Ustrlen(user), 0)) + *errptr = US"RADIUS: add user name failed"; + +else if (!rc_avpair_add(h, &send, PW_USER_PASSWORD, CS radius_args, + Ustrlen(radius_args), 0)) + *errptr = US"RADIUS: add password failed"; + +else if (!rc_avpair_add(h, &send, PW_SERVICE_TYPE, &service, 0, 0)) + *errptr = US"RADIUS: add service type failed"; + +#endif /* RADIUS_LIB_RADIUSCLIENT */ + +if (*errptr) + { + DEBUG(D_auth) debug_printf("%s\n", *errptr); + return ERROR; + } + +#ifdef RADIUS_LIB_RADIUSCLIENT +result = rc_auth(0, send, &received, msg); +#else +result = rc_auth(h, 0, send, &received, msg); +#endif + +DEBUG(D_auth) debug_printf("RADIUS code returned %d\n", result); + +switch (result) + { + case OK_RC: + return OK; + + case REJECT_RC: + case ERROR_RC: + return FAIL; + + case TIMEOUT_RC: + *errptr = US"RADIUS: timed out"; + return ERROR; + + case BADRESP_RC: + default: + *errptr = string_sprintf("RADIUS: unexpected response (%d)", result); + return ERROR; + } + +#else /* RADIUS_LIB_RADLIB is set */ + +/* Authenticate using the libradius library */ + +if (!(h = rad_auth_open())) + { + *errptr = string_sprintf("RADIUS: can't initialise libradius"); + return ERROR; + } +if (rad_config(h, RADIUS_CONFIG_FILE) != 0 || + rad_create_request(h, RAD_ACCESS_REQUEST) != 0 || + rad_put_string(h, RAD_USER_NAME, CS user) != 0 || + rad_put_string(h, RAD_USER_PASSWORD, CS radius_args) != 0 || + rad_put_int(h, RAD_SERVICE_TYPE, RAD_AUTHENTICATE_ONLY) != 0 || + rad_put_string(h, RAD_NAS_IDENTIFIER, CS primary_hostname) != 0) + { + *errptr = string_sprintf("RADIUS: %s", rad_strerror(h)); + result = ERROR; + } +else + switch (result = rad_send_request(h)) + { + case RAD_ACCESS_ACCEPT: + result = OK; + break; + + case RAD_ACCESS_REJECT: + result = FAIL; + break; + + case -1: + *errptr = string_sprintf("RADIUS: %s", rad_strerror(h)); + result = ERROR; + break; + + default: + *errptr = string_sprintf("RADIUS: unexpected response (%d)", result); + result= ERROR; + break; + } + +if (*errptr) DEBUG(D_auth) debug_printf("%s\n", *errptr); +rad_close(h); +return result; + +#endif /* RADIUS_LIB_RADLIB */ +} + +#endif /* RADIUS_CONFIG_FILE */ + +/* End of call_radius.c */ diff --git a/src/auths/check_serv_cond.c b/src/auths/check_serv_cond.c new file mode 100644 index 0000000..457a715 --- /dev/null +++ b/src/auths/check_serv_cond.c @@ -0,0 +1,124 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ + +#include "../exim.h" + +/* This module contains the function server_condition(), which is used +by all authenticators. */ + + +/************************************************* +* Check server_condition * +*************************************************/ + +/* This function is called from the server code of all authenticators. For +plaintext and gsasl, it is always called: the argument cannot be empty, because +for those, setting server_condition is what enables it as a server +authenticator. For all the other authenticators, this function is called after +they have authenticated, to enable additional authorization to be done. + +Argument: the authenticator's instance block + +Returns: + OK NULL argument, or success + DEFER couldn't complete the check + FAIL authentication failed +*/ + +int +auth_check_serv_cond(auth_instance *ablock) +{ + return auth_check_some_cond(ablock, + US"server_condition", ablock->server_condition, OK); +} + + +/************************************************* +* Check some server condition * +*************************************************/ + +/* This underlies server_condition, but is also used for some more generic + checks. + +Arguments: + ablock the authenticator's instance block + label debugging label naming the string checked + condition the condition string to be expanded and checked + unset value to return on NULL condition + +Returns: + OK success (or unset=OK) + DEFER couldn't complete the check + FAIL authentication failed +*/ + +int +auth_check_some_cond(auth_instance *ablock, + uschar *label, uschar *condition, int unset) +{ +uschar *cond; + +HDEBUG(D_auth) + { + debug_printf("%s authenticator %s:\n", ablock->name, label); + for (int i = 0; i < AUTH_VARS; i++) if (auth_vars[i]) + debug_printf(" $auth%d = %s\n", i + 1, auth_vars[i]); + for (int i = 1; i <= expand_nmax; i++) + debug_printf(" $%d = %.*s\n", i, expand_nlength[i], expand_nstring[i]); + debug_print_string(ablock->server_debug_string); /* customized debug */ + } + +/* For the plaintext authenticator, server_condition is never NULL. For the +rest, an unset condition lets everything through. */ + +/* For server_condition, an unset condition lets everything through. +For plaintext/gsasl authenticators, it will have been pre-checked to prevent +this. We return the unset scenario value given to us, which for +server_condition will be OK and otherwise will typically be FAIL. */ + +if (!condition) return unset; +cond = expand_string(condition); + +HDEBUG(D_auth) + if (!cond) + debug_printf("expansion failed: %s\n", expand_string_message); + else + debug_printf("expanded string: %s\n", cond); + +/* A forced expansion failure causes authentication to fail. Other expansion +failures yield DEFER, which will cause a temporary error code to be returned to +the AUTH command. The problem is at the server end, so the client should try +again later. */ + +if (!cond) + { + if (f.expand_string_forcedfail) return FAIL; + auth_defer_msg = expand_string_message; + return DEFER; + } + +/* Return FAIL for empty string, "0", "no", and "false"; return OK for +"1", "yes", and "true"; return DEFER for anything else, with the string +available as an error text for the user. */ + +if (*cond == 0 || + Ustrcmp(cond, "0") == 0 || + strcmpic(cond, US"no") == 0 || + strcmpic(cond, US"false") == 0) + return FAIL; + +if (Ustrcmp(cond, "1") == 0 || + strcmpic(cond, US"yes") == 0 || + strcmpic(cond, US"true") == 0) + return OK; + +auth_defer_msg = cond; +auth_defer_user_msg = string_sprintf(": %s", cond); +return DEFER; +} + +/* End of check_serv_cond.c */ diff --git a/src/auths/cram_md5.c b/src/auths/cram_md5.c new file mode 100644 index 0000000..2c0616c --- /dev/null +++ b/src/auths/cram_md5.c @@ -0,0 +1,360 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* Copyright (c) The Exim Maintainers 2020 */ +/* See the file NOTICE for conditions of use and distribution. */ + + +/* The stand-alone version just tests the algorithm. We have to drag +in the MD5 computation functions, without their own stand-alone main +program. */ + +#ifdef STAND_ALONE +#define CRAM_STAND_ALONE +#include "md5.c" + + +/* This is the normal, non-stand-alone case */ + +#else +#include "../exim.h" +#include "cram_md5.h" + +/* Options specific to the cram_md5 authentication mechanism. */ + +optionlist auth_cram_md5_options[] = { + { "client_name", opt_stringptr, + OPT_OFF(auth_cram_md5_options_block, client_name) }, + { "client_secret", opt_stringptr, + OPT_OFF(auth_cram_md5_options_block, client_secret) }, + { "server_secret", opt_stringptr, + OPT_OFF(auth_cram_md5_options_block, server_secret) } +}; + +/* Size of the options list. An extern variable has to be used so that its +address can appear in the tables drtables.c. */ + +int auth_cram_md5_options_count = + sizeof(auth_cram_md5_options)/sizeof(optionlist); + +/* Default private options block for the condition authentication method. */ + +auth_cram_md5_options_block auth_cram_md5_option_defaults = { + NULL, /* server_secret */ + NULL, /* client_secret */ + NULL /* client_name */ +}; + + +#ifdef MACRO_PREDEF + +/* Dummy values */ +void auth_cram_md5_init(auth_instance *ablock) {} +int auth_cram_md5_server(auth_instance *ablock, uschar *data) {return 0;} +int auth_cram_md5_client(auth_instance *ablock, void *sx, int timeout, + uschar *buffer, int buffsize) {return 0;} + +#else /*!MACRO_PREDEF*/ + + +/************************************************* +* Initialization entry point * +*************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + +void +auth_cram_md5_init(auth_instance *ablock) +{ +auth_cram_md5_options_block *ob = + (auth_cram_md5_options_block *)(ablock->options_block); +if (ob->server_secret != NULL) ablock->server = TRUE; +if (ob->client_secret != NULL) + { + ablock->client = TRUE; + if (ob->client_name == NULL) ob->client_name = primary_hostname; + } +} + +#endif /*!MACRO_PREDEF*/ +#endif /* STAND_ALONE */ + + + +#ifndef MACRO_PREDEF +/************************************************* +* Perform the CRAM-MD5 algorithm * +*************************************************/ + +/* The CRAM-MD5 algorithm is described in RFC 2195. It computes + + MD5((secret XOR opad), MD5((secret XOR ipad), challenge)) + +where secret is padded out to 64 characters (after being reduced to an MD5 +digest if longer than 64) and ipad and opad are 64-byte strings of 0x36 and +0x5c respectively, and comma means concatenation. + +Arguments: + secret the shared secret + challenge the challenge text + digest 16-byte slot to put the answer in + +Returns: nothing +*/ + +static void +compute_cram_md5(uschar *secret, uschar *challenge, uschar *digestptr) +{ +md5 base; +int len = Ustrlen(secret); +uschar isecret[64]; +uschar osecret[64]; +uschar md5secret[16]; + +/* If the secret is longer than 64 characters, we compute its MD5 digest +and use that. */ + +if (len > 64) + { + md5_start(&base); + md5_end(&base, US secret, len, md5secret); + secret = US md5secret; + len = 16; + } + +/* The key length is now known to be <= 64. Set up the padded and xor'ed +versions. */ + +memcpy(isecret, secret, len); +memset(isecret+len, 0, 64-len); +memcpy(osecret, isecret, 64); + +for (int i = 0; i < 64; i++) + { + isecret[i] ^= 0x36; + osecret[i] ^= 0x5c; + } + +/* Compute the inner MD5 digest */ + +md5_start(&base); +md5_mid(&base, isecret); +md5_end(&base, US challenge, Ustrlen(challenge), md5secret); + +/* Compute the outer MD5 digest */ + +md5_start(&base); +md5_mid(&base, osecret); +md5_end(&base, md5secret, 16, digestptr); +} + + +#ifndef STAND_ALONE + +/************************************************* +* Server entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_cram_md5_server(auth_instance *ablock, uschar *data) +{ +auth_cram_md5_options_block *ob = + (auth_cram_md5_options_block *)(ablock->options_block); +uschar *challenge = string_sprintf("<%d.%ld@%s>", getpid(), + (long int) time(NULL), primary_hostname); +uschar *clear, *secret; +uschar digest[16]; +int i, rc, len; + +/* If we are running in the test harness, always send the same challenge, +an example string taken from the RFC. */ + +if (f.running_in_test_harness) + challenge = US"<1896.697170952@postoffice.reston.mci.net>"; + +/* No data should have been sent with the AUTH command */ + +if (*data) return UNEXPECTED; + +/* Send the challenge, read the return */ + +if ((rc = auth_get_data(&data, challenge, Ustrlen(challenge))) != OK) return rc; +if ((len = b64decode(data, &clear)) < 0) return BAD64; + +/* The return consists of a user name, space-separated from the CRAM-MD5 +digest, expressed in hex. Extract the user name and put it in $auth1 and $1. +The former is now the preferred variable; the latter is the original one. Then +check that the remaining length is 32. */ + +auth_vars[0] = expand_nstring[1] = clear; +while (*clear && !isspace(*clear)) clear++; +if (!isspace(*clear)) return FAIL; +*clear++ = 0; + +expand_nlength[1] = clear - expand_nstring[1] - 1; +if (len - expand_nlength[1] - 1 != 32) return FAIL; +expand_nmax = 1; + +/* Expand the server_secret string so that it can compute a value dependent on +the user name if necessary. */ + +debug_print_string(ablock->server_debug_string); /* customized debugging */ +secret = expand_string(ob->server_secret); + +/* A forced fail implies failure of authentication - i.e. we have no secret for +the given name. */ + +if (secret == NULL) + { + if (f.expand_string_forcedfail) return FAIL; + auth_defer_msg = expand_string_message; + return DEFER; + } + +/* Compute the CRAM-MD5 digest that we should have received from the client. */ + +compute_cram_md5(secret, challenge, digest); + +HDEBUG(D_auth) + { + uschar buff[64]; + debug_printf("CRAM-MD5: user name = %s\n", auth_vars[0]); + debug_printf(" challenge = %s\n", challenge); + debug_printf(" received = %s\n", clear); + Ustrcpy(buff, US" digest = "); + for (i = 0; i < 16; i++) sprintf(CS buff+22+2*i, "%02x", digest[i]); + debug_printf("%.54s\n", buff); + } + +/* We now have to compare the digest, which is 16 bytes in binary, with the +data received, which is expressed in lower case hex. We checked above that +there were 32 characters of data left. */ + +for (i = 0; i < 16; i++) + { + int a = *clear++; + int b = *clear++; + if (((((a >= 'a')? a - 'a' + 10 : a - '0') << 4) + + ((b >= 'a')? b - 'a' + 10 : b - '0')) != digest[i]) return FAIL; + } + +/* Expand server_condition as an authorization check */ +return auth_check_serv_cond(ablock); +} + + + +/************************************************* +* Client entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_cram_md5_client( + auth_instance *ablock, /* authenticator block */ + void * sx, /* smtp connextion */ + int timeout, /* command timeout */ + uschar *buffer, /* for reading response */ + int buffsize) /* size of buffer */ +{ +auth_cram_md5_options_block *ob = + (auth_cram_md5_options_block *)(ablock->options_block); +uschar *secret = expand_string(ob->client_secret); +uschar *name = expand_string(ob->client_name); +uschar *challenge, *p; +int i; +uschar digest[16]; + +/* If expansion of either the secret or the user name failed, return CANCELLED +or ERROR, as appropriate. */ + +if (!secret || !name) + { + if (f.expand_string_forcedfail) + { + *buffer = 0; /* No message */ + return CANCELLED; + } + string_format(buffer, buffsize, "expansion of \"%s\" failed in " + "%s authenticator: %s", + !secret ? ob->client_secret : ob->client_name, + ablock->name, expand_string_message); + return ERROR; + } + +/* Initiate the authentication exchange and read the challenge, which arrives +in base 64. */ + +if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0) + return FAIL_SEND; +if (!smtp_read_response(sx, buffer, buffsize, '3', timeout)) + return FAIL; + +if (b64decode(buffer + 4, &challenge) < 0) + { + string_format(buffer, buffsize, "bad base 64 string in challenge: %s", + big_buffer + 4); + return ERROR; + } + +/* Run the CRAM-MD5 algorithm on the secret and the challenge */ + +compute_cram_md5(secret, challenge, digest); + +/* Create the response from the user name plus the CRAM-MD5 digest */ + +string_format(big_buffer, big_buffer_size - 36, "%s", name); +for (p = big_buffer; *p; ) p++; +*p++ = ' '; + +for (i = 0; i < 16; i++) + p += sprintf(CS p, "%02x", digest[i]); + +/* Send the response, in base 64, and check the result. The response is +in big_buffer, but b64encode() returns its result in working store, +so calling smtp_write_command(), which uses big_buffer, is OK. */ + +buffer[0] = 0; +if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(CUS big_buffer, + p - big_buffer)) < 0) return FAIL_SEND; + +return smtp_read_response(sx, US buffer, buffsize, '2', timeout) + ? OK : FAIL; +} +#endif /* STAND_ALONE */ + + +/************************************************* +************************************************** +* Stand-alone test program * +************************************************** +*************************************************/ + +#ifdef STAND_ALONE + +int main(int argc, char **argv) +{ +int i; +uschar *secret = US argv[1]; +uschar *challenge = US argv[2]; +uschar digest[16]; + +compute_cram_md5(secret, challenge, digest); + +for (i = 0; i < 16; i++) printf("%02x", digest[i]); +printf("\n"); + +return 0; +} + +#endif + +#endif /*!MACRO_PREDEF*/ +/* End of cram_md5.c */ diff --git a/src/auths/cram_md5.h b/src/auths/cram_md5.h new file mode 100644 index 0000000..95644db --- /dev/null +++ b/src/auths/cram_md5.h @@ -0,0 +1,31 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Private structure for the private options. */ + +typedef struct { + uschar *server_secret; + uschar *client_secret; + uschar *client_name; +} auth_cram_md5_options_block; + +/* Data for reading the private options. */ + +extern optionlist auth_cram_md5_options[]; +extern int auth_cram_md5_options_count; + +/* Block containing default values. */ + +extern auth_cram_md5_options_block auth_cram_md5_option_defaults; + +/* The entry points for the mechanism */ + +extern void auth_cram_md5_init(auth_instance *); +extern int auth_cram_md5_server(auth_instance *, uschar *); +extern int auth_cram_md5_client(auth_instance *, void *, int, uschar *, int); + +/* End of cram_md5.h */ diff --git a/src/auths/cyrus_sasl.c b/src/auths/cyrus_sasl.c new file mode 100644 index 0000000..c8e2da5 --- /dev/null +++ b/src/auths/cyrus_sasl.c @@ -0,0 +1,513 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2020 - 2022 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* This code was originally contributed by Matthew Byng-Maddick */ + +/* Copyright (c) A L Digital 2004 */ + +/* A generic (mechanism independent) Cyrus SASL authenticator. */ + + +#include "../exim.h" + + +/* We can't just compile this code and allow the library mechanism to omit the +functions if they are not wanted, because we need to have the Cyrus SASL header +available for compiling. Therefore, compile these functions only if +AUTH_CYRUS_SASL is defined. However, some compilers don't like compiling empty +modules, so keep them happy with a dummy when skipping the rest. Make it +reference itself to stop picky compilers complaining that it is unused, and put +in a dummy argument to stop even pickier compilers complaining about infinite +loops. */ + +#ifndef AUTH_CYRUS_SASL +static void dummy(int x); +static void dummy2(int x) { dummy(x-1); } +static void dummy(int x) { dummy2(x-1); } +#else + + +#include <sasl/sasl.h> +#include "cyrus_sasl.h" + +/* Options specific to the cyrus_sasl authentication mechanism. */ + +optionlist auth_cyrus_sasl_options[] = { + { "server_hostname", opt_stringptr, + OPT_OFF(auth_cyrus_sasl_options_block, server_hostname) }, + { "server_mech", opt_stringptr, + OPT_OFF(auth_cyrus_sasl_options_block, server_mech) }, + { "server_realm", opt_stringptr, + OPT_OFF(auth_cyrus_sasl_options_block, server_realm) }, + { "server_service", opt_stringptr, + OPT_OFF(auth_cyrus_sasl_options_block, server_service) } +}; + +/* Size of the options list. An extern variable has to be used so that its +address can appear in the tables drtables.c. */ + +int auth_cyrus_sasl_options_count = + sizeof(auth_cyrus_sasl_options)/sizeof(optionlist); + +/* Default private options block for the cyrus_sasl authentication method. */ + +auth_cyrus_sasl_options_block auth_cyrus_sasl_option_defaults = { + US"smtp", /* server_service */ + US"$primary_hostname", /* server_hostname */ + NULL, /* server_realm */ + NULL /* server_mech */ +}; + + +#ifdef MACRO_PREDEF + +/* Dummy values */ +void auth_cyrus_sasl_init(auth_instance *ablock) {} +int auth_cyrus_sasl_server(auth_instance *ablock, uschar *data) {return 0;} +int auth_cyrus_sasl_client(auth_instance *ablock, void * sx, + int timeout, uschar *buffer, int buffsize) {return 0;} +gstring * auth_cyrus_sasl_version_report(gstring * g) {return NULL;} + +#else /*!MACRO_PREDEF*/ + + + + +/************************************************* +* Initialization entry point * +*************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + + +/* Auxiliary function, passed in data to sasl_server_init(). */ + +static int +mysasl_config(void *context, const char *plugin_name, const char *option, + const char **result, unsigned int *len) +{ +if (context && !strcmp(option, "mech_list")) + { + *result = context; + if (len) *len = strlen(*result); + return SASL_OK; + } +return SASL_FAIL; +} + +/* Here's the real function */ + +void +auth_cyrus_sasl_init(auth_instance *ablock) +{ +auth_cyrus_sasl_options_block *ob = + (auth_cyrus_sasl_options_block *)(ablock->options_block); +const uschar *list, *listptr, *buffer; +int rc, i; +unsigned int len; +rmark rs_point; +uschar *expanded_hostname; +char *realm_expanded; + +sasl_conn_t *conn; +sasl_callback_t cbs[] = { + {SASL_CB_GETOPT, NULL, NULL }, + {SASL_CB_LIST_END, NULL, NULL}}; + +/* default the mechanism to our "public name" */ + +if (!ob->server_mech) ob->server_mech = string_copy(ablock->public_name); + +if (!(expanded_hostname = expand_string(ob->server_hostname))) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "couldn't expand server_hostname [%s]: %s", + ablock->name, ob->server_hostname, expand_string_message); + +realm_expanded = NULL; +if ( ob->server_realm + && !(realm_expanded = CS expand_string(ob->server_realm))) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "couldn't expand server_realm [%s]: %s", + ablock->name, ob->server_realm, expand_string_message); + +/* we're going to initialise the library to check that there is an +authenticator of type whatever mechanism we're using */ + +cbs[0].proc = (int(*)(void)) &mysasl_config; +cbs[0].context = ob->server_mech; + +if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "couldn't initialise Cyrus SASL library.", ablock->name); + +if ((rc = sasl_server_new(CS ob->server_service, CS expanded_hostname, + realm_expanded, NULL, NULL, NULL, 0, &conn)) != SASL_OK) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "couldn't initialise Cyrus SASL server connection.", ablock->name); + +if ((rc = sasl_listmech(conn, NULL, "", ":", "", CCSS &list, &len, &i)) != SASL_OK) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "couldn't get Cyrus SASL mechanism list.", ablock->name); + +i = ':'; +listptr = list; + +HDEBUG(D_auth) + { + debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n", + ob->server_service, expanded_hostname, realm_expanded); + debug_printf("Cyrus SASL knows mechanisms: %s\n", list); + } + +/* the store_get / store_reset mechanism is hierarchical + the hierarchy is stored for us behind our back. This point + creates a hierarchy point for this function. */ + +rs_point = store_mark(); + +/* loop until either we get to the end of the list, or we match the +public name of this authenticator */ + +while ( (buffer = string_nextinlist(&listptr, &i, NULL, 0)) + && strcmpic(buffer,ob->server_mech) ); + +if (!buffer) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech); + +store_reset(rs_point); + +HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name); + +/* make sure that if we get here then we're allowed to advertise. */ +ablock->server = TRUE; + +sasl_dispose(&conn); +sasl_done(); +} + +/************************************************* +* Server entry point * +*************************************************/ + +/* For interface, see auths/README */ + +/* note, we don't care too much about memory allocation in this, because this is entirely +within a shortlived child */ + +int +auth_cyrus_sasl_server(auth_instance *ablock, uschar *data) +{ +auth_cyrus_sasl_options_block *ob = + (auth_cyrus_sasl_options_block *)(ablock->options_block); +uschar *output, *out2, *input, *clear, *hname; +uschar *debug = NULL; /* Stops compiler complaining */ +sasl_callback_t cbs[] = {{SASL_CB_LIST_END, NULL, NULL}}; +sasl_conn_t *conn; +char * realm_expanded = NULL; +int rc, firsttime = 1, clen, *negotiated_ssf_ptr = NULL, negotiated_ssf; +unsigned int inlen, outlen; + +input = data; +inlen = Ustrlen(data); + +HDEBUG(D_auth) debug = string_copy(data); + +hname = expand_string(ob->server_hostname); +if (hname && ob->server_realm) + realm_expanded = CS expand_string(ob->server_realm); +if (!hname || !realm_expanded && ob->server_realm) + { + auth_defer_msg = expand_string_message; + return DEFER; + } + +if (inlen) + { + if ((clen = b64decode(input, &clear)) < 0) + return BAD64; + input = clear; + inlen = clen; + } + +if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK) + { + auth_defer_msg = US"couldn't initialise Cyrus SASL library"; + return DEFER; + } + +rc = sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL, + NULL, NULL, 0, &conn); + +HDEBUG(D_auth) + debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n", + ob->server_service, hname, realm_expanded); + +if (rc != SASL_OK ) + { + auth_defer_msg = US"couldn't initialise Cyrus SASL connection"; + sasl_done(); + return DEFER; + } + +if (tls_in.cipher) + { + if ((rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits)) != SASL_OK) + { + HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n", + tls_in.bits, sasl_errstring(rc, NULL, NULL)); + auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF"; + sasl_done(); + return DEFER; + } + else + HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits); + + /*XXX Set channel-binding here with sasl_channel_binding_t / SASL_CHANNEL_BINDING + Unclear what the "name" element does though, ditto the "critical" flag. */ + } +else + HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n"); + +/* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly +annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed +with their iptostring() function, which just wraps +getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the +inet_ntop which we wrap in our host_ntoa() function. + +So the docs are too strict and we shouldn't worry about :: contractions. */ + +/* Set properties for remote and local host-ip;port */ +for (int i = 0; i < 2; ++i) + { + int propnum; + const uschar * label; + uschar * address_port; + const char *s_err; + + if (i) + { + propnum = SASL_IPREMOTEPORT; + label = CUS"peer"; + address_port = string_sprintf("%s;%d", + sender_host_address, sender_host_port); + } + else + { + propnum = SASL_IPLOCALPORT; + label = CUS"local"; + address_port = string_sprintf("%s;%d", interface_address, interface_port); + } + + if ((rc = sasl_setprop(conn, propnum, address_port)) != SASL_OK) + { + HDEBUG(D_auth) + { + s_err = sasl_errdetail(conn); + debug_printf("Failed to set %s SASL property: [%d] %s\n", + label, rc, s_err ? s_err : "<unknown reason>"); + } + break; + } + HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n", + label, address_port); + } + +for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; ) + { + if (firsttime) + { + firsttime = 0; + HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug); + rc = sasl_server_start(conn, CS ob->server_mech, inlen ? CS input : NULL, inlen, + CCSS &output, &outlen); + } + else + { + /* auth_get_data() takes a length-specfied block of binary + which can include zeroes; no terminating NUL is needed */ + + if ((rc = auth_get_data(&input, output, outlen)) != OK) + { + /* we couldn't get the data, so free up the library before + returning whatever error we get */ + sasl_dispose(&conn); + sasl_done(); + return rc; + } + inlen = Ustrlen(input); + + HDEBUG(D_auth) debug = string_copy(input); + if (inlen) + { + if ((clen = b64decode(input, &clear)) < 0) + { + sasl_dispose(&conn); + sasl_done(); + return BAD64; + } + input = clear; + inlen = clen; + } + + HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug); + rc = sasl_server_step(conn, CS input, inlen, CCSS &output, &outlen); + } + + if (rc == SASL_BADPROT) + { + sasl_dispose(&conn); + sasl_done(); + return UNEXPECTED; + } + if (rc == SASL_CONTINUE) + continue; + + /* Get the username and copy it into $auth1 and $1. The former is now the + preferred variable; the latter is the original variable. */ + + if ((sasl_getprop(conn, SASL_USERNAME, (const void **)&out2)) != SASL_OK) + { + HDEBUG(D_auth) + debug_printf("Cyrus SASL library will not tell us the username: %s\n", + sasl_errstring(rc, NULL, NULL)); + log_write(0, LOG_REJECT, "%s authenticator (%s): " + "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech, + sasl_errstring(rc, NULL, NULL)); + sasl_dispose(&conn); + sasl_done(); + return FAIL; + } + auth_vars[0] = expand_nstring[1] = string_copy(out2); + expand_nlength[1] = Ustrlen(out2); + expand_nmax = 1; + + switch (rc) + { + case SASL_FAIL: case SASL_BUFOVER: case SASL_BADMAC: case SASL_BADAUTH: + case SASL_NOAUTHZ: case SASL_ENCRYPT: case SASL_EXPIRED: + case SASL_DISABLED: case SASL_NOUSER: + /* these are considered permanent failure codes */ + HDEBUG(D_auth) + debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL)); + log_write(0, LOG_REJECT, "%s authenticator (%s): " + "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech, + sasl_errstring(rc, NULL, NULL)); + sasl_dispose(&conn); + sasl_done(); + return FAIL; + + case SASL_NOMECH: + /* this is a temporary failure, because the mechanism is not + available for this user. If it wasn't available at all, we + shouldn't have got here in the first place... */ + + HDEBUG(D_auth) + debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL)); + auth_defer_msg = + string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech); + sasl_dispose(&conn); + sasl_done(); + return DEFER; + + case SASL_OK: + HDEBUG(D_auth) + debug_printf("Cyrus SASL %s authentication succeeded for %s\n", + ob->server_mech, auth_vars[0]); + + if ((rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr)))!= SASL_OK) + { + HDEBUG(D_auth) + debug_printf("Cyrus SASL library will not tell us the SSF: %s\n", + sasl_errstring(rc, NULL, NULL)); + log_write(0, LOG_REJECT, "%s authenticator (%s): " + "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech, + sasl_errstring(rc, NULL, NULL)); + sasl_dispose(&conn); + sasl_done(); + return FAIL; + } + negotiated_ssf = *negotiated_ssf_ptr; + HDEBUG(D_auth) + debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf); + if (negotiated_ssf > 0) + { + HDEBUG(D_auth) + debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf); + log_write(0, LOG_REJECT, "%s authenticator (%s): " + "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf); + sasl_dispose(&conn); + sasl_done(); + return FAIL; + } + + /* close down the connection, freeing up library's memory */ + sasl_dispose(&conn); + sasl_done(); + + /* Expand server_condition as an authorization check */ + return auth_check_serv_cond(ablock); + + default: + /* Anything else is a temporary failure, and we'll let SASL print out + * the error string for us + */ + HDEBUG(D_auth) + debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL)); + auth_defer_msg = + string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL)); + sasl_dispose(&conn); + sasl_done(); + return DEFER; + } + } +/* NOTREACHED */ +return 0; /* Stop compiler complaints */ +} + +/************************************************* +* Diagnostic API * +*************************************************/ + +gstring * +auth_cyrus_sasl_version_report(gstring * g) +{ +const char * implementation, * version; +sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL); +g = string_fmt_append(g, + "Library version: Cyrus SASL: Compile: %d.%d.%d\n" + " Runtime: %s [%s]\n", + SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, + version, implementation); +return g; +} + +/************************************************* +* Client entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_cyrus_sasl_client( + auth_instance *ablock, /* authenticator block */ + void * sx, /* connexction */ + int timeout, /* command timeout */ + uschar *buffer, /* for reading response */ + int buffsize) /* size of buffer */ +{ +/* We don't support clients (yet) in this implementation of cyrus_sasl */ +return FAIL; +} + +#endif /*!MACRO_PREDEF*/ +#endif /* AUTH_CYRUS_SASL */ + +/* End of cyrus_sasl.c */ diff --git a/src/auths/cyrus_sasl.h b/src/auths/cyrus_sasl.h new file mode 100644 index 0000000..6cf8834 --- /dev/null +++ b/src/auths/cyrus_sasl.h @@ -0,0 +1,36 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2022 */ +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Copyright (c) A L Digital Ltd 2004 */ + +/* Private structure for the private options. */ + +typedef struct { + uschar *server_service; + uschar *server_hostname; + uschar *server_realm; + uschar *server_mech; +} auth_cyrus_sasl_options_block; + +/* Data for reading the private options. */ + +extern optionlist auth_cyrus_sasl_options[]; +extern int auth_cyrus_sasl_options_count; + +/* Block containing default values. */ + +extern auth_cyrus_sasl_options_block auth_cyrus_sasl_option_defaults; + +/* The entry points for the mechanism */ + +extern void auth_cyrus_sasl_init(auth_instance *); +extern int auth_cyrus_sasl_server(auth_instance *, uschar *); +extern int auth_cyrus_sasl_client(auth_instance *, void *, int, uschar *, int); +extern gstring * auth_cyrus_sasl_version_report(gstring *); + +/* End of cyrus_sasl.h */ diff --git a/src/auths/dovecot.c b/src/auths/dovecot.c new file mode 100644 index 0000000..5d77133 --- /dev/null +++ b/src/auths/dovecot.c @@ -0,0 +1,530 @@ +/* + * Copyright (c) The Exim Maintainers 2006 - 2022 + * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* A number of modifications have been made to the original code. Originally I +commented them specially, but now they are getting quite extensive, so I have +ceased doing that. The biggest change is to use unbuffered I/O on the socket +because using C buffered I/O gives problems on some operating systems. PH */ + +/* Protocol specifications: + * Dovecot 1, protocol version 1.1 + * http://wiki.dovecot.org/Authentication%20Protocol + * + * Dovecot 2, protocol version 1.1 + * http://wiki2.dovecot.org/Design/AuthProtocol + */ + +#include "../exim.h" +#include "dovecot.h" + +#define VERSION_MAJOR 1 +#define VERSION_MINOR 0 + +/* http://wiki.dovecot.org/Authentication%20Protocol +"The maximum line length isn't defined, + but it's currently expected to fit into 8192 bytes" +*/ +#define DOVECOT_AUTH_MAXLINELEN 8192 + +/* This was hard-coded as 8. +AUTH req C->S sends {"AUTH", id, mechanism, service } + params, 5 defined for +Dovecot 1; Dovecot 2 (same protocol version) defines 9. + +Master->Server sends {"USER", id, userid} + params, 6 defined. +Server->Client only gives {"OK", id} + params, unspecified, only 1 guaranteed. + +We only define here to accept S->C; max seen is 3+<unspecified>, plus the two +for the command and id, where unspecified might include _at least_ user=... + +So: allow for more fields than we ever expect to see, while aware that count +can go up without changing protocol version. +The cost is the length of an array of pointers on the stack. +*/ +#define DOVECOT_AUTH_MAXFIELDCOUNT 16 + +/* Options specific to the authentication mechanism. */ +optionlist auth_dovecot_options[] = { + { "server_socket", opt_stringptr, OPT_OFF(auth_dovecot_options_block, server_socket) }, +/*{ "server_tls", opt_bool, OPT_OFF(auth_dovecot_options_block, server_tls) },*/ +}; + +/* Size of the options list. An extern variable has to be used so that its +address can appear in the tables drtables.c. */ + +int auth_dovecot_options_count = nelem(auth_dovecot_options); + +/* Default private options block for the authentication method. */ + +auth_dovecot_options_block auth_dovecot_option_defaults = { + .server_socket = NULL, +/* .server_tls = FALSE,*/ +}; + + + + +#ifdef MACRO_PREDEF + +/* Dummy values */ +void auth_dovecot_init(auth_instance *ablock) {} +int auth_dovecot_server(auth_instance *ablock, uschar *data) {return 0;} +int auth_dovecot_client(auth_instance *ablock, void * sx, + int timeout, uschar *buffer, int buffsize) {return 0;} + +#else /*!MACRO_PREDEF*/ + + +/* Static variables for reading from the socket */ + +static uschar sbuffer[256]; +static int socket_buffer_left; + + + +/************************************************* + * Initialization entry point * + *************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + +void auth_dovecot_init(auth_instance *ablock) +{ +auth_dovecot_options_block *ob = + (auth_dovecot_options_block *)(ablock->options_block); + +if (!ablock->public_name) ablock->public_name = ablock->name; +if (ob->server_socket) ablock->server = TRUE; +ablock->client = FALSE; +} + +/************************************************* + * "strcut" to split apart server lines * + *************************************************/ + +/* Dovecot auth protocol uses TAB \t as delimiter; a line consists +of a command-name, TAB, and then any parameters, each separated by a TAB. +A parameter can be param=value or a bool, just param. + +This function modifies the original str in-place, inserting NUL characters. +It initialises ptrs entries, setting all to NULL and only setting +non-NULL N entries, where N is the return value, the number of fields seen +(one more than the number of tabs). + +Note that the return value will always be at least 1, is the count of +actual fields (so last valid offset into ptrs is one less). +*/ + +static int +strcut(uschar *str, uschar **ptrs, int nptrs) +{ +uschar *last_sub_start = str; +int n; + +for (n = 0; n < nptrs; n++) + ptrs[n] = NULL; +n = 1; + +while (*str) + if (*str++ == '\t') + if (n++ <= nptrs) + { + *ptrs++ = last_sub_start; + last_sub_start = str; + str[-1] = '\0'; + } + +/* It's acceptable for the string to end with a tab character. We see +this in AUTH PLAIN without an initial response from the client, which +causing us to send "334 " and get the data from the client. */ +if (n <= nptrs) + *ptrs = last_sub_start; +else + { + HDEBUG(D_auth) + debug_printf("dovecot: warning: too many results from tab-splitting;" + " saw %d fields, room for %d\n", n, nptrs); + n = nptrs; + } + +return n <= nptrs ? n : nptrs; +} + +static void debug_strcut(uschar **ptrs, int nlen, int alen) ARG_UNUSED; +static void +debug_strcut(uschar **ptrs, int nlen, int alen) +{ +int i; +debug_printf("%d read but unreturned bytes; strcut() gave %d results: ", + socket_buffer_left, nlen); +for (i = 0; i < nlen; i++) + debug_printf(" {%s}", ptrs[i]); +if (nlen < alen) + debug_printf(" last is %s\n", ptrs[i] ? ptrs[i] : US"<null>"); +else + debug_printf(" (max for capacity)\n"); +} + +#define CHECK_COMMAND(str, arg_min, arg_max) do { \ + if (strcmpic(US(str), args[0]) != 0) \ + goto out; \ + if (nargs - 1 < (arg_min)) \ + goto out; \ + if ( (arg_max != -1) && (nargs - 1 > (arg_max)) ) \ + goto out; \ +} while (0) + +#define OUT(msg) do { \ + auth_defer_msg = (US msg); \ + goto out; \ +} while(0) + + + +/************************************************* +* "fgets" to read directly from socket * +*************************************************/ + +/* Added by PH after a suggestion by Steve Usher because the previous use of +C-style buffered I/O gave trouble. */ + +static uschar * +dc_gets(uschar *s, int n, client_conn_ctx * cctx) +{ +int p = 0; +int count = 0; + +for (;;) + { + if (socket_buffer_left == 0) + { + if ((socket_buffer_left = +#ifndef DISABLE_TLS + cctx->tls_ctx ? tls_read(cctx->tls_ctx, sbuffer, sizeof(sbuffer)) : +#endif + read(cctx->sock, sbuffer, sizeof(sbuffer))) <= 0) + if (count == 0) + return NULL; + else + break; + p = 0; + } + + while (p < socket_buffer_left) + { + if (count >= n - 1) break; + s[count++] = sbuffer[p]; + if (sbuffer[p++] == '\n') break; + } + + memmove(sbuffer, sbuffer + p, socket_buffer_left - p); + socket_buffer_left -= p; + + if (s[count-1] == '\n' || count >= n - 1) break; + } + +s[count] = '\0'; +return s; +} + + + + +/************************************************* +* Server entry point * +*************************************************/ + +int +auth_dovecot_server(auth_instance * ablock, uschar * data) +{ +auth_dovecot_options_block *ob = + (auth_dovecot_options_block *) ablock->options_block; +uschar buffer[DOVECOT_AUTH_MAXLINELEN]; +uschar *args[DOVECOT_AUTH_MAXFIELDCOUNT]; +uschar *auth_command; +uschar *auth_extra_data = US""; +uschar *p; +int nargs, tmp; +int crequid = 1, ret = DEFER; +host_item host; +client_conn_ctx cctx = {.sock = -1, .tls_ctx = NULL}; +BOOL found = FALSE, have_mech_line = FALSE; + +HDEBUG(D_auth) debug_printf("dovecot authentication\n"); + +if (!data) + { + ret = FAIL; + goto out; + } + +/*XXX timeout? */ +cctx.sock = ip_streamsocket(ob->server_socket, &auth_defer_msg, 5, &host); +if (cctx.sock < 0) + goto out; + +#ifdef notdef +# ifndef DISABLE_TLS +if (ob->server_tls) + { + union sockaddr_46 interface_sock; + EXIM_SOCKLEN_T size = sizeof(interface_sock); + smtp_connect_args conn_args = { .host = &host }; + tls_support tls_dummy = { .sni = NULL }; + uschar * errstr; + + if (getsockname(cctx->sock, (struct sockaddr *) &interface_sock, &size) == 0) + conn_args.sending_ip_address = host_ntoa(-1, &interface_sock, NULL, NULL); + else + { + *errmsg = string_sprintf("getsockname failed: %s", strerror(errno)); + goto bad; + } + + if (!tls_client_start(&cctx, &conn_args, NULL, &tls_dummy, &errstr)) + { + auth_defer_msg = string_sprintf("TLS connect failed: %s", errstr); + goto out; + } + } +# endif +#endif + +auth_defer_msg = US"authentication socket protocol error"; + +socket_buffer_left = 0; /* Global, used to read more than a line but return by line */ +for (;;) + { +debug_printf("%s %d\n", __FUNCTION__, __LINE__); + if (!dc_gets(buffer, sizeof(buffer), &cctx)) + OUT("authentication socket read error or premature eof"); +debug_printf("%s %d\n", __FUNCTION__, __LINE__); + p = buffer + Ustrlen(buffer) - 1; + if (*p != '\n') + OUT("authentication socket protocol line too long"); + + *p = '\0'; + HDEBUG(D_auth) debug_printf("received: '%s'\n", buffer); + + nargs = strcut(buffer, args, nelem(args)); + + HDEBUG(D_auth) debug_strcut(args, nargs, nelem(args)); + + /* Code below rewritten by Kirill Miazine (km@krot.org). Only check commands that + Exim will need. Original code also failed if Dovecot server sent unknown + command. E.g. COOKIE in version 1.1 of the protocol would cause troubles. */ + /* pdp: note that CUID is a per-connection identifier sent by the server, + which increments at server discretion. + By contrast, the "id" field of the protocol is a connection-specific request + identifier, which needs to be unique per request from the client and is not + connected to the CUID value, so we ignore CUID from server. It's purely for + diagnostics. */ + + if (Ustrcmp(args[0], US"VERSION") == 0) + { + CHECK_COMMAND("VERSION", 2, 2); + if (Uatoi(args[1]) != VERSION_MAJOR) + OUT("authentication socket protocol version mismatch"); + } + else if (Ustrcmp(args[0], US"MECH") == 0) + { + CHECK_COMMAND("MECH", 1, INT_MAX); + have_mech_line = TRUE; + if (strcmpic(US args[1], ablock->public_name) == 0) + found = TRUE; + } + else if (Ustrcmp(args[0], US"SPID") == 0) + { + /* Unfortunately the auth protocol handshake wasn't designed well + to differentiate between auth-client/userdb/master. auth-userdb + and auth-master send VERSION + SPID lines only and nothing + afterwards, while auth-client sends VERSION + MECH + SPID + + CUID + more. The simplest way that we can determine if we've + connected to the correct socket is to see if MECH line exists or + not (alternatively we'd have to have a small timeout after SPID + to see if CUID is sent or not). */ + + if (!have_mech_line) + OUT("authentication socket type mismatch" + " (connected to auth-master instead of auth-client)"); + } + else if (Ustrcmp(args[0], US"DONE") == 0) + { + CHECK_COMMAND("DONE", 0, 0); + break; + } + } + +if (!found) + { + auth_defer_msg = string_sprintf( + "Dovecot did not advertise mechanism \"%s\" to us", ablock->public_name); + goto out; + } + +/* Added by PH: data must not contain tab (as it is +b64 it shouldn't, but check for safety). */ + +if (Ustrchr(data, '\t') != NULL) + { + ret = FAIL; + goto out; + } + +/* Added by PH: extra fields when TLS is in use or if the TCP/IP +connection is local. */ + +if (tls_in.cipher) + auth_extra_data = string_sprintf("secured\t%s%s", + tls_in.certificate_verified ? "valid-client-cert" : "", + tls_in.certificate_verified ? "\t" : ""); + +else if ( interface_address + && Ustrcmp(sender_host_address, interface_address) == 0) + auth_extra_data = US"secured\t"; + + +/**************************************************************************** +The code below was the original code here. It didn't work. A reading of the +file auth-protocol.txt.gz that came with Dovecot 1.0_beta8 indicated that +this was not right. Maybe something changed. I changed it to move the +service indication into the AUTH command, and it seems to be better. PH + +fprintf(f, "VERSION\t%d\t%d\r\nSERVICE\tSMTP\r\nCPID\t%d\r\n" + "AUTH\t%d\t%s\trip=%s\tlip=%s\tresp=%s\r\n", + VERSION_MAJOR, VERSION_MINOR, getpid(), cuid, + ablock->public_name, sender_host_address, interface_address, + data ? CS data : ""); + +Subsequently, the command was modified to add "secured" and "valid-client- +cert" when relevant. +****************************************************************************/ + +auth_command = string_sprintf("VERSION\t%d\t%d\nCPID\t%d\n" + "AUTH\t%d\t%s\tservice=smtp\t%srip=%s\tlip=%s\tnologin\tresp=%s\n", + VERSION_MAJOR, VERSION_MINOR, getpid(), crequid, + ablock->public_name, auth_extra_data, sender_host_address, + interface_address, data); + +if (( +#ifndef DISABLE_TLS + cctx.tls_ctx ? tls_write(cctx.tls_ctx, auth_command, Ustrlen(auth_command), FALSE) : +#endif + write(cctx.sock, auth_command, Ustrlen(auth_command))) < 0) + HDEBUG(D_auth) debug_printf("error sending auth_command: %s\n", + strerror(errno)); + +HDEBUG(D_auth) debug_printf("sent: '%s'\n", auth_command); + +while (1) + { + uschar *temp; + uschar *auth_id_pre = NULL; + + if (!dc_gets(buffer, sizeof(buffer), &cctx)) + { + auth_defer_msg = US"authentication socket read error or premature eof"; + goto out; + } + + buffer[Ustrlen(buffer) - 1] = 0; + HDEBUG(D_auth) debug_printf("received: '%s'\n", buffer); + nargs = strcut(buffer, args, nelem(args)); + HDEBUG(D_auth) debug_strcut(args, nargs, nelem(args)); + + if (Uatoi(args[1]) != crequid) + OUT("authentication socket connection id mismatch"); + + switch (toupper(*args[0])) + { + case 'C': + CHECK_COMMAND("CONT", 1, 2); + + if ((tmp = auth_get_no64_data(&data, US args[2])) != OK) + { + ret = tmp; + goto out; + } + + /* Added by PH: data must not contain tab (as it is + b64 it shouldn't, but check for safety). */ + + if (Ustrchr(data, '\t') != NULL) + { + ret = FAIL; + goto out; + } + + temp = string_sprintf("CONT\t%d\t%s\n", crequid, data); + if (( +#ifndef DISABLE_TLS + cctx.tls_ctx ? tls_write(cctx.tls_ctx, temp, Ustrlen(temp), FALSE) : +#endif + write(cctx.sock, temp, Ustrlen(temp))) < 0) + OUT("authentication socket write error"); + break; + + case 'F': + CHECK_COMMAND("FAIL", 1, -1); + + for (int i = 2; i < nargs && !auth_id_pre; i++) + if (Ustrncmp(args[i], US"user=", 5) == 0) + { + auth_id_pre = args[i] + 5; + expand_nstring[1] = auth_vars[0] = string_copy(auth_id_pre); /* PH */ + expand_nlength[1] = Ustrlen(auth_id_pre); + expand_nmax = 1; + } + ret = FAIL; + goto out; + + case 'O': + CHECK_COMMAND("OK", 2, -1); + + /* Search for the "user=$USER" string in the args array + and return the proper value. */ + + for (int i = 2; i < nargs && !auth_id_pre; i++) + if (Ustrncmp(args[i], US"user=", 5) == 0) + { + auth_id_pre = args[i] + 5; + expand_nstring[1] = auth_vars[0] = string_copy(auth_id_pre); /* PH */ + expand_nlength[1] = Ustrlen(auth_id_pre); + expand_nmax = 1; + } + + if (!auth_id_pre) + OUT("authentication socket protocol error, username missing"); + + auth_defer_msg = NULL; + ret = OK; + /* fallthrough */ + + default: + goto out; + } + } + +out: +/* close the socket used by dovecot */ +#ifndef DISABLE_TLS +if (cctx.tls_ctx) + tls_close(cctx.tls_ctx, TRUE); +#endif +if (cctx.sock >= 0) + close(cctx.sock); + +/* Expand server_condition as an authorization check */ +return ret == OK ? auth_check_serv_cond(ablock) : ret; +} + + +#endif /*!MACRO_PREDEF*/ diff --git a/src/auths/dovecot.h b/src/auths/dovecot.h new file mode 100644 index 0000000..bfe1f07 --- /dev/null +++ b/src/auths/dovecot.h @@ -0,0 +1,30 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) The Exim Maintainters 2020 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Private structure for the private options. */ + +typedef struct { + uschar * server_socket; + BOOL server_tls; +} auth_dovecot_options_block; + +/* Data for reading the private options. */ + +extern optionlist auth_dovecot_options[]; +extern int auth_dovecot_options_count; + +/* Block containing default values. */ + +extern auth_dovecot_options_block auth_dovecot_option_defaults; + +/* The entry points for the mechanism */ + +extern void auth_dovecot_init(auth_instance *); +extern int auth_dovecot_server(auth_instance *, uschar *); + +/* End of dovecot.h */ diff --git a/src/auths/external.c b/src/auths/external.c new file mode 100644 index 0000000..7e7fca8 --- /dev/null +++ b/src/auths/external.c @@ -0,0 +1,155 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Jeremy Harris 2019-2020 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* This file provides an Exim authenticator driver for +a server to verify a client SSL certificate, using the EXTERNAL +method defined in RFC 4422 Appendix A. +*/ + + +#include "../exim.h" +#include "external.h" + +/* Options specific to the external authentication mechanism. */ + +optionlist auth_external_options[] = { + { "client_send", opt_stringptr, OPT_OFF(auth_external_options_block, client_send) }, + { "server_param2", opt_stringptr, OPT_OFF(auth_external_options_block, server_param2) }, + { "server_param3", opt_stringptr, OPT_OFF(auth_external_options_block, server_param3) }, +}; + +/* Size of the options list. An extern variable has to be used so that its +address can appear in the tables drtables.c. */ + +int auth_external_options_count = nelem(auth_external_options); + +/* Default private options block for the authentication method. */ + +auth_external_options_block auth_external_option_defaults = { + .server_param2 = NULL, + .server_param3 = NULL, + + .client_send = NULL, +}; + + +#ifdef MACRO_PREDEF + +/* Dummy values */ +void auth_external_init(auth_instance *ablock) {} +int auth_external_server(auth_instance *ablock, uschar *data) {return 0;} +int auth_external_client(auth_instance *ablock, void * sx, + int timeout, uschar *buffer, int buffsize) {return 0;} + +#else /*!MACRO_PREDEF*/ + + + + +/************************************************* +* Initialization entry point * +*************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + +void +auth_external_init(auth_instance *ablock) +{ +auth_external_options_block * ob = (auth_external_options_block *)ablock->options_block; +if (!ablock->public_name) ablock->public_name = ablock->name; +if (ablock->server_condition) ablock->server = TRUE; +if (ob->client_send) ablock->client = TRUE; +} + + + +/************************************************* +* Server entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_external_server(auth_instance * ablock, uschar * data) +{ +auth_external_options_block * ob = (auth_external_options_block *)ablock->options_block; +int rc; + +/* If data was supplied on the AUTH command, decode it, and split it up into +multiple items at binary zeros. The strings are put into $auth1, $auth2, etc, +up to a maximum. To retain backwards compatibility, they are also put int $1, +$2, etc. If the data consists of the string "=" it indicates a single, empty +string. */ + +if (*data) + if ((rc = auth_read_input(data)) != OK) + return rc; + +/* Now go through the list of prompt strings. Skip over any whose data has +already been provided as part of the AUTH command. For the rest, send them +out as prompts, and get a data item back. If the data item is "*", abandon the +authentication attempt. Otherwise, split it into items as above. */ + +if (expand_nmax == 0) /* skip if rxd data */ + if ((rc = auth_prompt(CUS"")) != OK) + return rc; + +if (ob->server_param2) + { + uschar * s = expand_string(ob->server_param2); + auth_vars[expand_nmax] = s; + expand_nstring[++expand_nmax] = s; + expand_nlength[expand_nmax] = Ustrlen(s); + if (ob->server_param3) + { + s = expand_string(ob->server_param3); + auth_vars[expand_nmax] = s; + expand_nstring[++expand_nmax] = s; + expand_nlength[expand_nmax] = Ustrlen(s); + } + } + +return auth_check_serv_cond(ablock); +} + + + +/************************************************* +* Client entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_external_client( + auth_instance *ablock, /* authenticator block */ + void * sx, /* smtp connextion */ + int timeout, /* command timeout */ + uschar *buffer, /* buffer for reading response */ + int buffsize) /* size of buffer */ +{ +auth_external_options_block *ob = + (auth_external_options_block *)(ablock->options_block); +const uschar * text = ob->client_send; +int rc; + +/* We output an AUTH command with one expanded argument, the client_send option */ + +if ((rc = auth_client_item(sx, ablock, &text, AUTH_ITEM_FIRST | AUTH_ITEM_LAST, + timeout, buffer, buffsize)) != OK) + return rc == DEFER ? FAIL : rc; + +if (text) auth_vars[0] = string_copy(text); +return OK; +} + + + +#endif /*!MACRO_PREDEF*/ +/* End of external.c */ diff --git a/src/auths/external.h b/src/auths/external.h new file mode 100644 index 0000000..7d43650 --- /dev/null +++ b/src/auths/external.h @@ -0,0 +1,32 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Jeremy Harris 2019 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Private structure for the private options. */ + +typedef struct { + uschar * server_param2; + uschar * server_param3; + + uschar * client_send; +} auth_external_options_block; + +/* Data for reading the private options. */ + +extern optionlist auth_external_options[]; +extern int auth_external_options_count; + +/* Block containing default values. */ + +extern auth_external_options_block auth_external_option_defaults; + +/* The entry points for the mechanism */ + +extern void auth_external_init(auth_instance *); +extern int auth_external_server(auth_instance *, uschar *); +extern int auth_external_client(auth_instance *, void *, int, uschar *, int); + +/* End of external.h */ diff --git a/src/auths/get_data.c b/src/auths/get_data.c new file mode 100644 index 0000000..e0d79db --- /dev/null +++ b/src/auths/get_data.c @@ -0,0 +1,259 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* Copyright (c) The Exim Maintainers 2020 - 2021 */ +/* See the file NOTICE for conditions of use and distribution. */ + +#include "../exim.h" + + +/**************************************************************** +* Decode and split the argument of an AUTH command * +****************************************************************/ + +/* If data was supplied on the AUTH command, decode it, and split it up into +multiple items at binary zeros. The strings are put into $auth1, $auth2, etc, +up to a maximum. To retain backwards compatibility, they are also put int $1, +$2, etc. If the data consists of the string "=" it indicates a single, empty +string. */ + +int +auth_read_input(const uschar * data) +{ +if (Ustrcmp(data, "=") == 0) + { + auth_vars[0] = expand_nstring[++expand_nmax] = US""; + expand_nlength[expand_nmax] = 0; + } +else + { + uschar * clear, * end; + int len; + + if ((len = b64decode(data, &clear)) < 0) return BAD64; + DEBUG(D_auth) debug_printf("auth input decode:"); + for (end = clear + len; clear < end && expand_nmax < EXPAND_MAXN; ) + { + DEBUG(D_auth) debug_printf(" '%s'", clear); + if (expand_nmax < AUTH_VARS) auth_vars[expand_nmax] = clear; + expand_nstring[++expand_nmax] = clear; + while (*clear != 0) clear++; + expand_nlength[expand_nmax] = clear++ - expand_nstring[expand_nmax]; + } + DEBUG(D_auth) debug_printf("\n"); + } +return OK; +} + + + + +/************************************************* +* Issue a challenge and get a response * +*************************************************/ + +/* This function is used by authentication drivers to b64-encode and +output a challenge to the SMTP client, and read the response line. + +Arguments: + aptr set to point to the response (which is in big_buffer) + challenge the challenge data (unencoded, may be binary) + challen the length of the challenge data, in bytes + +Returns: OK on success + BAD64 if response too large for buffer + CANCELLED if response is "*" +*/ + +int +auth_get_data(uschar ** aptr, const uschar * challenge, int challen) +{ +int c; +int p = 0; +smtp_printf("334 %s\r\n", FALSE, b64encode(challenge, challen)); +while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF) + { + if (p >= big_buffer_size - 1) return BAD64; + big_buffer[p++] = c; + } +if (p > 0 && big_buffer[p-1] == '\r') p--; +big_buffer[p] = 0; +DEBUG(D_receive) debug_printf("SMTP<< %s\n", big_buffer); +if (Ustrcmp(big_buffer, "*") == 0) return CANCELLED; +*aptr = big_buffer; +return OK; +} + + + +int +auth_prompt(const uschar * challenge) +{ +int rc, len; +uschar * resp, * clear, * end; + +if ((rc = auth_get_data(&resp, challenge, Ustrlen(challenge))) != OK) + return rc; +if ((len = b64decode(resp, &clear)) < 0) + return BAD64; +end = clear + len; + +/* This loop must run at least once, in case the length is zero */ +do + { + if (expand_nmax < AUTH_VARS) auth_vars[expand_nmax] = clear; + expand_nstring[++expand_nmax] = clear; + while (*clear != 0) clear++; + expand_nlength[expand_nmax] = clear++ - expand_nstring[expand_nmax]; + } +while (clear < end && expand_nmax < EXPAND_MAXN); +return OK; +} + + +/*********************************************** +* Send an AUTH-negotiation item * +************************************************/ + +/* Expand and send one client auth item and read the response. +Include the AUTH command and method if tagged as "first". Use the given buffer +for receiving the b6-encoded reply; decode it it return it in the string arg. + +Return: + OK success + FAIL_SEND error after writing a command; errno is set + FAIL failed after reading a response; + either errno is set (for timeouts, I/O failures) or + the buffer contains the SMTP response line + CANCELLED the client cancelled authentication (often "fail" in expansion) + the buffer may contain a message; if not, *buffer = 0 + ERROR local problem (typically expansion error); message in buffer + DEFER more items expected +*/ + +int +auth_client_item(void * sx, auth_instance * ablock, const uschar ** inout, + unsigned flags, int timeout, uschar * buffer, int buffsize) +{ +int len, clear_len; +uschar * ss, * clear; + +ss = US expand_cstring(*inout); +if (ss == *inout) ss = string_copy(ss); + +/* Forced expansion failure is not an error; authentication is abandoned. On +all but the first string, we have to abandon the authentication attempt by +sending a line containing "*". Save the failed expansion string, because it +is in big_buffer, and that gets used by the sending function. */ + +if (!ss) + { + if (!(flags & AUTH_ITEM_FIRST)) + { + if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0) + (void) smtp_read_response(sx, US buffer, buffsize, '2', timeout); + } + if (f.expand_string_forcedfail) + { + *buffer = 0; /* No message */ + return CANCELLED; + } + string_format(buffer, buffsize, "expansion of \"%s\" failed in %s " + "authenticator: %s", *inout, ablock->name, expand_string_message); + return ERROR; + } + +len = Ustrlen(ss); + +/* The character ^ is used as an escape for a binary zero character, which is +needed for the PLAIN mechanism. It must be doubled if really needed. + +The parsing ambiguity of ^^^ is taken as ^^ -> ^ ; ^ -> NUL - and there is +no way to get a leading ^ after a NUL. We would need to intro new syntax to +support that (probably preferring to take a more-standard exim list as a source +and concat the elements with intervening NULs. Either a magic marker on the +source string for client_send, or a new option). */ + +for (int i = 0; i < len; i++) + if (ss[i] == '^') + if (ss[i+1] != '^') + ss[i] = 0; + else + if (--len > i+1) memmove(ss + i + 1, ss + i + 2, len - i); + +/* The first string is attached to the AUTH command; others are sent +unembellished. */ + +if (flags & AUTH_ITEM_FIRST) + { + if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s%s%s\r\n", + ablock->public_name, len == 0 ? "" : " ", b64encode(CUS ss, len)) < 0) + return FAIL_SEND; + } +else + if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(CUS ss, len)) < 0) + return FAIL_SEND; + +/* If we receive a success response from the server, authentication +has succeeded. There may be more data to send, but is there any point +in provoking an error here? */ + +if (smtp_read_response(sx, buffer, buffsize, '2', timeout)) + { + *inout = NULL; + return OK; + } + +/* Not a success response. If errno != 0 there is some kind of transmission +error. Otherwise, check the response code in the buffer. If it starts with +'3', more data is expected. */ + +if (errno != 0 || buffer[0] != '3') return FAIL; + +/* If there is no more data to send, we have to cancel the authentication +exchange and return ERROR. */ + +if (flags & AUTH_ITEM_LAST) + { + if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0) + (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout); + string_format(buffer, buffsize, "Too few items in client_send in %s " + "authenticator", ablock->name); + return ERROR; + } + +/* Now that we know we'll continue, we put the received data into $auth<n>, +if possible. First, decode it: buffer+4 skips over the SMTP status code. */ + +clear_len = b64decode(buffer+4, &clear); + +/* If decoding failed, the default is to terminate the authentication, and +return FAIL, with the SMTP response still in the buffer. However, if client_ +ignore_invalid_base64 is set, we ignore the error, and put an empty string +into $auth<n>. */ + +if (clear_len < 0) + { + uschar *save_bad = string_copy(buffer); + if (!(flags & AUTH_ITEM_IGN64)) + { + if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0) + (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout); + string_format(buffer, buffsize, "Invalid base64 string in server " + "response \"%s\"", save_bad); + return CANCELLED; + } + DEBUG(D_auth) debug_printf("bad b64 decode for '%s';" + " ignoring due to client_ignore_invalid_base64\n", save_bad); + clear = string_copy(US""); + clear_len = 0; + } + +*inout = clear; +return DEFER; +} + + +/* End of get_data.c */ diff --git a/src/auths/get_no64_data.c b/src/auths/get_no64_data.c new file mode 100644 index 0000000..a019756 --- /dev/null +++ b/src/auths/get_no64_data.c @@ -0,0 +1,47 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* See the file NOTICE for conditions of use and distribution. */ + +#include "../exim.h" + + +/************************************************* +* Issue a non-b64 challenge and get a response * +*************************************************/ + +/* This function is used by authentication drivers to output a challenge +to the SMTP client and read the response line. This version does not use base +64 encoding for the text on the 334 line. It is used by the SPA, dovecot +and gsasl authenticators. + +Arguments: + aptr set to point to the response (which is in big_buffer) + challenge the challenge text (unencoded) + +Returns: OK on success + BAD64 if response too large for buffer + CANCELLED if response is "*" +*/ + +int +auth_get_no64_data(uschar **aptr, uschar *challenge) +{ +int c; +int p = 0; +smtp_printf("334 %s\r\n", FALSE, challenge); +while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF) + { + if (p >= big_buffer_size - 1) return BAD64; + big_buffer[p++] = c; + } +if (p > 0 && big_buffer[p-1] == '\r') p--; +big_buffer[p] = 0; +if (Ustrcmp(big_buffer, "*") == 0) return CANCELLED; +*aptr = big_buffer; +return OK; +} + +/* End of get_no64_data.c */ diff --git a/src/auths/gsasl_exim.c b/src/auths/gsasl_exim.c new file mode 100644 index 0000000..bae5f08 --- /dev/null +++ b/src/auths/gsasl_exim.c @@ -0,0 +1,1024 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2019 - 2022 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Copyright (c) Twitter Inc 2012 + Author: Phil Pennock <pdp@exim.org> */ +/* Copyright (c) Phil Pennock 2012 */ + +/* Interface to GNU SASL library for generic authentication. */ + +/* Trade-offs: + +GNU SASL does not provide authentication data itself, so we have to expose +that decision to configuration. For some mechanisms, we need to act much +like plaintext. For others, we only need to be able to provide some +evaluated data on demand. There's no abstracted way (ie, without hardcoding +knowledge of authenticators here) to know which need what properties; we +can't query a session or the library for "we will need these for mechanism X". + +So: we always require server_condition, even if sometimes it will just be +set as "yes". We do provide a number of other hooks, which might not make +sense in all contexts. For some, we can do checks at init time. +*/ + +#include "../exim.h" + +#ifndef AUTH_GSASL +/* dummy function to satisfy compilers when we link in an "empty" file. */ +static void dummy(int x); +static void dummy2(int x) { dummy(x-1); } +static void dummy(int x) { dummy2(x-1); } +#else + +#include <gsasl.h> +#include "gsasl_exim.h" + + +#if GSASL_VERSION_MINOR >= 10 +# define EXIM_GSASL_HAVE_SCRAM_SHA_256 +# define EXIM_GSASL_SCRAM_S_KEY + +#elif GSASL_VERSION_MINOR == 9 +# define EXIM_GSASL_HAVE_SCRAM_SHA_256 + +# if GSASL_VERSION_PATCH >= 1 +# define EXIM_GSASL_SCRAM_S_KEY +# endif +# if GSASL_VERSION_PATCH < 2 +# define CHANNELBIND_HACK +# endif + +#else +# define CHANNELBIND_HACK +#endif + +/* Convenience for testing strings */ + +#define STREQIC(Foo, Bar) (strcmpic((Foo), (Bar)) == 0) + + +/* Authenticator-specific options. */ +/* I did have server_*_condition options for various mechanisms, but since +we only ever handle one mechanism at a time, I didn't see the point in keeping +that. In case someone sees a point, I've left the condition_check() API +alone. */ +#define LOFF(field) OPT_OFF(auth_gsasl_options_block, field) + +optionlist auth_gsasl_options[] = { + { "client_authz", opt_stringptr, LOFF(client_authz) }, + { "client_channelbinding", opt_bool, LOFF(client_channelbinding) }, + { "client_password", opt_stringptr, LOFF(client_password) }, + { "client_spassword", opt_stringptr, LOFF(client_spassword) }, + { "client_username", opt_stringptr, LOFF(client_username) }, + + { "server_channelbinding", opt_bool, LOFF(server_channelbinding) }, + { "server_hostname", opt_stringptr, LOFF(server_hostname) }, +#ifdef EXIM_GSASL_SCRAM_S_KEY + { "server_key", opt_stringptr, LOFF(server_key) }, +#endif + { "server_mech", opt_stringptr, LOFF(server_mech) }, + { "server_password", opt_stringptr, LOFF(server_password) }, + { "server_realm", opt_stringptr, LOFF(server_realm) }, + { "server_scram_iter", opt_stringptr, LOFF(server_scram_iter) }, + { "server_scram_salt", opt_stringptr, LOFF(server_scram_salt) }, +#ifdef EXIM_GSASL_SCRAM_S_KEY + { "server_skey", opt_stringptr, LOFF(server_s_key) }, +#endif + { "server_service", opt_stringptr, LOFF(server_service) } +}; + +int auth_gsasl_options_count = + sizeof(auth_gsasl_options)/sizeof(optionlist); + +/* Defaults for the authenticator-specific options. */ +auth_gsasl_options_block auth_gsasl_option_defaults = { + .server_service = US"smtp", + .server_hostname = US"$primary_hostname", + .server_scram_iter = US"4096", + /* all others zero/null */ +}; + + +#ifdef MACRO_PREDEF +# include "../macro_predef.h" + +/* Dummy values */ +void auth_gsasl_init(auth_instance *ablock) {} +int auth_gsasl_server(auth_instance *ablock, uschar *data) {return 0;} +int auth_gsasl_client(auth_instance *ablock, void * sx, + int timeout, uschar *buffer, int buffsize) {return 0;} +gstring * auth_gsasl_version_report(gstring * g) {return NULL;} + +void +auth_gsasl_macros(void) +{ +# ifdef EXIM_GSASL_HAVE_SCRAM_SHA_256 + builtin_macro_create(US"_HAVE_AUTH_GSASL_SCRAM_SHA_256"); +# endif +# ifdef EXIM_GSASL_SCRAM_S_KEY + builtin_macro_create(US"_HAVE_AUTH_GSASL_SCRAM_S_KEY"); +# endif +} + +#else /*!MACRO_PREDEF*/ + + + +/* "Globals" for managing the gsasl interface. */ + +static Gsasl *gsasl_ctx = NULL; +static int + main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop); +static int + server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock); +static int + client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock); + +static BOOL sasl_error_should_defer = FALSE; +static Gsasl_property callback_loop = 0; +static BOOL checked_server_condition = FALSE; + +enum { CURRENTLY_SERVER = 1, CURRENTLY_CLIENT = 2 }; + +struct callback_exim_state { + auth_instance *ablock; + int currently; +}; + + +/************************************************* +* Initialization entry point * +*************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + +void +auth_gsasl_init(auth_instance *ablock) +{ +static char * once = NULL; +int rc; +auth_gsasl_options_block *ob = + (auth_gsasl_options_block *)(ablock->options_block); + +/* As per existing Cyrus glue, use the authenticator's public name as +the default for the mechanism name; we don't handle multiple mechanisms +in one authenticator, but the same driver can be used multiple times. */ + +if (!ob->server_mech) + ob->server_mech = string_copy(ablock->public_name); + +/* Can get multiple session contexts from one library context, so just +initialise the once. */ + +if (!gsasl_ctx) + { + if ((rc = gsasl_init(&gsasl_ctx)) != GSASL_OK) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "couldn't initialise GNU SASL library: %s (%s)", + ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc)); + + gsasl_callback_set(gsasl_ctx, main_callback); + } + +/* We don't need this except to log it for debugging. */ + +HDEBUG(D_auth) if (!once) + { + if ((rc = gsasl_server_mechlist(gsasl_ctx, &once)) != GSASL_OK) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "failed to retrieve list of mechanisms: %s (%s)", + ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc)); + + debug_printf("GNU SASL supports: %s\n", once); + } + +if (!gsasl_client_support_p(gsasl_ctx, CCS ob->server_mech)) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "GNU SASL does not support mechanism \"%s\"", + ablock->name, ob->server_mech); + +if (ablock->server_condition) + ablock->server = TRUE; +else if( ob->server_mech + && !STREQIC(ob->server_mech, US"EXTERNAL") + && !STREQIC(ob->server_mech, US"ANONYMOUS") + && !STREQIC(ob->server_mech, US"PLAIN") + && !STREQIC(ob->server_mech, US"LOGIN") + ) + { + /* At present, for mechanisms we don't panic on absence of server_condition; + need to figure out the most generically correct approach to deciding when + it's critical and when it isn't. Eg, for simple validation (PLAIN mechanism, + etc) it clearly is critical. + */ + + ablock->server = FALSE; + HDEBUG(D_auth) debug_printf("%s authenticator: " + "Need server_condition for %s mechanism\n", + ablock->name, ob->server_mech); + } + +/* This does *not* scale to new SASL mechanisms. Need a better way to ask +which properties will be needed. */ + +if ( !ob->server_realm + && STREQIC(ob->server_mech, US"DIGEST-MD5")) + { + ablock->server = FALSE; + HDEBUG(D_auth) debug_printf("%s authenticator: " + "Need server_realm for %s mechanism\n", + ablock->name, ob->server_mech); + } + +ablock->client = ob->client_username && ob->client_password; +} + + +/* GNU SASL uses one top-level callback, registered at library level. +We dispatch to client and server functions instead. */ + +static int +main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) +{ +int rc = 0; +struct callback_exim_state *cb_state = + (struct callback_exim_state *)gsasl_session_hook_get(sctx); + +if (!cb_state) + { + HDEBUG(D_auth) debug_printf("gsasl callback (%d) not from our server/client processing\n", prop); +#ifdef CHANNELBIND_HACK + if (prop == GSASL_CB_TLS_UNIQUE) + { + uschar * s; + if ((s = gsasl_callback_hook_get(ctx))) + { + HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE from ctx hook\n"); + gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CS s); + } + else + { + HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE! dummy for now\n"); + gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, ""); + } + return GSASL_OK; + } +#endif + return GSASL_NO_CALLBACK; + } + +HDEBUG(D_auth) + debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n", + prop, callback_loop); + +if (callback_loop > 0) + { + /* Most likely is that we were asked for property FOO, and to + expand the string we asked for property BAR to put into an auth + variable, but property BAR is not supplied for this mechanism. */ + HDEBUG(D_auth) + debug_printf("Loop, asked for property %d while handling property %d\n", + prop, callback_loop); + return GSASL_NO_CALLBACK; + } +callback_loop = prop; + +if (cb_state->currently == CURRENTLY_CLIENT) + rc = client_callback(ctx, sctx, prop, cb_state->ablock); +else if (cb_state->currently == CURRENTLY_SERVER) + rc = server_callback(ctx, sctx, prop, cb_state->ablock); +else + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "unhandled callback state, bug in Exim", cb_state->ablock->name); + /* NOTREACHED */ + +callback_loop = 0; +return rc; +} + + +/************************************************* +* Debug service function * +*************************************************/ +static const uschar * +gsasl_prop_code_to_name(Gsasl_property prop) +{ +switch (prop) + { + case GSASL_AUTHID: return US"AUTHID"; + case GSASL_AUTHZID: return US"AUTHZID"; + case GSASL_PASSWORD: return US"PASSWORD"; + case GSASL_ANONYMOUS_TOKEN: return US"ANONYMOUS_TOKEN"; + case GSASL_SERVICE: return US"SERVICE"; + case GSASL_HOSTNAME: return US"HOSTNAME"; + case GSASL_GSSAPI_DISPLAY_NAME: return US"GSSAPI_DISPLAY_NAME"; + case GSASL_PASSCODE: return US"PASSCODE"; + case GSASL_SUGGESTED_PIN: return US"SUGGESTED_PIN"; + case GSASL_PIN: return US"PIN"; + case GSASL_REALM: return US"REALM"; + case GSASL_DIGEST_MD5_HASHED_PASSWORD: return US"DIGEST_MD5_HASHED_PASSWORD"; + case GSASL_QOPS: return US"QOPS"; + case GSASL_QOP: return US"QOP"; + case GSASL_SCRAM_ITER: return US"SCRAM_ITER"; + case GSASL_SCRAM_SALT: return US"SCRAM_SALT"; + case GSASL_SCRAM_SALTED_PASSWORD: return US"SCRAM_SALTED_PASSWORD"; +#ifdef EXIM_GSASL_SCRAM_S_KEY + case GSASL_SCRAM_STOREDKEY: return US"SCRAM_STOREDKEY"; + case GSASL_SCRAM_SERVERKEY: return US"SCRAM_SERVERKEY"; +#endif + case GSASL_CB_TLS_UNIQUE: return US"CB_TLS_UNIQUE"; + case GSASL_SAML20_IDP_IDENTIFIER: return US"SAML20_IDP_IDENTIFIER"; + case GSASL_SAML20_REDIRECT_URL: return US"SAML20_REDIRECT_URL"; + case GSASL_OPENID20_REDIRECT_URL: return US"OPENID20_REDIRECT_URL"; + case GSASL_OPENID20_OUTCOME_DATA: return US"OPENID20_OUTCOME_DATA"; + case GSASL_SAML20_AUTHENTICATE_IN_BROWSER: return US"SAML20_AUTHENTICATE_IN_BROWSER"; + case GSASL_OPENID20_AUTHENTICATE_IN_BROWSER: return US"OPENID20_AUTHENTICATE_IN_BROWSER"; + case GSASL_VALIDATE_SIMPLE: return US"VALIDATE_SIMPLE"; + case GSASL_VALIDATE_EXTERNAL: return US"VALIDATE_EXTERNAL"; + case GSASL_VALIDATE_ANONYMOUS: return US"VALIDATE_ANONYMOUS"; + case GSASL_VALIDATE_GSSAPI: return US"VALIDATE_GSSAPI"; + case GSASL_VALIDATE_SECURID: return US"VALIDATE_SECURID"; + case GSASL_VALIDATE_SAML20: return US"VALIDATE_SAML20"; + case GSASL_VALIDATE_OPENID20: return US"VALIDATE_OPENID20"; + } +return CUS string_sprintf("(unknown prop: %d)", (int)prop); +} + +/************************************************* +* Server entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_gsasl_server(auth_instance *ablock, uschar *initial_data) +{ +char *tmps; +char *to_send, *received; +Gsasl_session *sctx = NULL; +auth_gsasl_options_block *ob = + (auth_gsasl_options_block *)(ablock->options_block); +struct callback_exim_state cb_state; +int rc, auth_result, exim_error, exim_error_override; + +HDEBUG(D_auth) + debug_printf("GNU SASL: initialising session for %s, mechanism %s\n", + ablock->name, ob->server_mech); + +#ifndef DISABLE_TLS +if (tls_in.channelbinding && ob->server_channelbinding) + { +# ifndef DISABLE_TLS_RESUME + if (!tls_in.ext_master_secret && tls_in.resumption == RESUME_USED) + { /* per RFC 7677 section 4 */ + HDEBUG(D_auth) debug_printf( + "channel binding not usable on resumed TLS without extended-master-secret"); + return FAIL; + } +# endif +# ifdef CHANNELBIND_HACK +/* This is a gross hack to get around the library before 1.9.2 +a) requiring that c-b was already set, at the _start() call, and +b) caching a b64'd version of the binding then which it never updates. */ + + gsasl_callback_hook_set(gsasl_ctx, tls_in.channelbinding); +# endif + } +#endif + +if ((rc = gsasl_server_start(gsasl_ctx, CCS ob->server_mech, &sctx)) != GSASL_OK) + { + auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)", + gsasl_strerror_name(rc), gsasl_strerror(rc)); + HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg); + return DEFER; + } +/* Hereafter: gsasl_finish(sctx) please */ + +cb_state.ablock = ablock; +cb_state.currently = CURRENTLY_SERVER; +gsasl_session_hook_set(sctx, &cb_state); + +tmps = CS expand_string(ob->server_service); +gsasl_property_set(sctx, GSASL_SERVICE, tmps); +tmps = CS expand_string(ob->server_hostname); +gsasl_property_set(sctx, GSASL_HOSTNAME, tmps); +if (ob->server_realm) + { + tmps = CS expand_string(ob->server_realm); + if (tmps && *tmps) + gsasl_property_set(sctx, GSASL_REALM, tmps); + } +/* We don't support protection layers. */ +gsasl_property_set(sctx, GSASL_QOPS, "qop-auth"); + +#ifndef DISABLE_TLS +if (tls_in.channelbinding) + { + /* Some auth mechanisms can ensure that both sides are talking withing the + same security context; for TLS, this means that even if a bad certificate + has been accepted, they remain MitM-proof because both sides must be within + the same negotiated session; if someone is terminating one session and + proxying data on within a second, authentication will fail. + + We might not have this available, depending upon TLS implementation, + ciphersuite, phase of moon ... + + If we do, it results in extra SASL mechanisms being available; here, + Exim's one-mechanism-per-authenticator potentially causes problems. + It depends upon how GNU SASL will implement the PLUS variants of GS2 + and whether it automatically mandates a switch to the bound PLUS + if the data is available. Since default-on, despite being more secure, + would then result in mechanism name changes on a library update, we + have little choice but to default it off and let the admin choose to + enable it. *sigh* + + Earlier library versions need this set early, during the _start() call, + so we had to misuse gsasl_callback_hook_set/get() as a data transfer + mech for the callback done at that time to get the bind-data. More recently + the callback is done (if needed) during the first gsasl_stop(). We know + the bind-data here so can set it (and should not get a callback). + */ + if (ob->server_channelbinding) + { + HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n", + ablock->name); +# ifndef CHANNELBIND_HACK + gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_in.channelbinding); +# endif + } + else + HDEBUG(D_auth) + debug_printf("Auth %s: Not enabling channel-binding (data available)\n", + ablock->name); + } +else + HDEBUG(D_auth) + debug_printf("Auth %s: no channel-binding data available\n", + ablock->name); +#endif + +checked_server_condition = FALSE; + +received = CS initial_data; +to_send = NULL; +exim_error = exim_error_override = OK; + +do { + switch (rc = gsasl_step64(sctx, received, &to_send)) + { + case GSASL_OK: + if (!to_send) + goto STOP_INTERACTION; + break; + + case GSASL_NEEDS_MORE: + break; + + case GSASL_AUTHENTICATION_ERROR: + case GSASL_INTEGRITY_ERROR: + case GSASL_NO_AUTHID: + case GSASL_NO_ANONYMOUS_TOKEN: + case GSASL_NO_AUTHZID: + case GSASL_NO_PASSWORD: + case GSASL_NO_PASSCODE: + case GSASL_NO_PIN: + case GSASL_BASE64_ERROR: + HDEBUG(D_auth) debug_printf("GNU SASL permanent error: %s (%s)\n", + gsasl_strerror_name(rc), gsasl_strerror(rc)); + log_write(0, LOG_REJECT, "%s authenticator (%s):\n " + "GNU SASL permanent failure: %s (%s)", + ablock->name, ob->server_mech, + gsasl_strerror_name(rc), gsasl_strerror(rc)); + if (rc == GSASL_BASE64_ERROR) + exim_error_override = BAD64; + goto STOP_INTERACTION; + + default: + auth_defer_msg = string_sprintf("GNU SASL temporary error: %s (%s)", + gsasl_strerror_name(rc), gsasl_strerror(rc)); + HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg); + exim_error_override = DEFER; + goto STOP_INTERACTION; + } + + /*XXX having our caller send the final smtp "235" is unfortunate; wastes a roundtrip */ + if ((rc == GSASL_NEEDS_MORE) || (to_send && *to_send)) + exim_error = auth_get_no64_data(USS &received, US to_send); + + if (to_send) + { + free(to_send); + to_send = NULL; + } + + if (exim_error) + break; /* handles * cancelled check */ + + } while (rc == GSASL_NEEDS_MORE); + +STOP_INTERACTION: +auth_result = rc; + +HDEBUG(D_auth) + { + const uschar * s; + if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_ITER))) + debug_printf(" - itercnt: '%s'\n", s); + if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SALT))) + debug_printf(" - salt: '%s'\n", s); +#ifdef EXIM_GSASL_SCRAM_S_KEY + if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SERVERKEY))) + debug_printf(" - ServerKey: '%s'\n", s); + if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_STOREDKEY))) + debug_printf(" - StoredKey: '%s'\n", s); +#endif + } + +gsasl_finish(sctx); + +/* Can return: OK DEFER FAIL CANCELLED BAD64 UNEXPECTED */ + +if (exim_error != OK) + return exim_error; + +if (auth_result != GSASL_OK) + { + HDEBUG(D_auth) debug_printf("authentication returned %s (%s)\n", + gsasl_strerror_name(auth_result), gsasl_strerror(auth_result)); + if (exim_error_override != OK) + return exim_error_override; /* might be DEFER */ + if (sasl_error_should_defer) /* overriding auth failure SASL error */ + return DEFER; + return FAIL; + } + +/* Auth succeeded, check server_condition unless already done in callback */ +return checked_server_condition ? OK : auth_check_serv_cond(ablock); +} + + +/* returns the GSASL status of expanding the Exim string given */ +static int +condition_check(auth_instance *ablock, uschar *label, uschar *condition_string) +{ +int exim_rc = auth_check_some_cond(ablock, label, condition_string, FAIL); +switch (exim_rc) + { + case OK: return GSASL_OK; + case DEFER: sasl_error_should_defer = TRUE; + return GSASL_AUTHENTICATION_ERROR; + case FAIL: return GSASL_AUTHENTICATION_ERROR; + default: log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "Unhandled return from checking %s: %d", + ablock->name, label, exim_rc); + } + +/* NOTREACHED */ +return GSASL_AUTHENTICATION_ERROR; +} + + +/* Set the "next" $auth[n] and increment expand_nmax */ + +static void +set_exim_authvar_from_prop(Gsasl_session * sctx, Gsasl_property prop) +{ +uschar * propval = US gsasl_property_fast(sctx, prop); +int i = expand_nmax, j = i + 1; +propval = propval ? string_copy(propval) : US""; +HDEBUG(D_auth) debug_printf("auth[%d] <= %s'%s'\n", + j, gsasl_prop_code_to_name(prop), propval); +expand_nstring[j] = propval; +expand_nlength[j] = Ustrlen(propval); +if (i < AUTH_VARS) auth_vars[i] = propval; +expand_nmax = j; +} + +static void +set_exim_authvars_from_a_az_r_props(Gsasl_session * sctx) +{ +if (expand_nmax > 0 ) return; + +/* Asking for GSASL_AUTHZID calls back into us if we use +gsasl_property_get(), thus the use of gsasl_property_fast(). +Do we really want to hardcode limits per mechanism? What happens when +a new mechanism is added to the library. It *shouldn't* result in us +needing to add more glue, since avoiding that is a large part of the +point of SASL. */ + +set_exim_authvar_from_prop(sctx, GSASL_AUTHID); +set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); +set_exim_authvar_from_prop(sctx, GSASL_REALM); +} + + +static int +prop_from_option(Gsasl_session * sctx, Gsasl_property prop, + const uschar * option) +{ +HDEBUG(D_auth) debug_printf(" %s\n", gsasl_prop_code_to_name(prop)); +if (option) + { + set_exim_authvars_from_a_az_r_props(sctx); + option = expand_cstring(option); + HDEBUG(D_auth) debug_printf(" '%s'\n", option); + if (*option) + gsasl_property_set(sctx, prop, CCS option); + return GSASL_OK; + } +HDEBUG(D_auth) debug_printf(" option not set\n"); +return GSASL_NO_CALLBACK; +} + +static int +server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, + auth_instance *ablock) +{ +char * tmps; +uschar * s; +int cbrc = GSASL_NO_CALLBACK; +auth_gsasl_options_block * ob = + (auth_gsasl_options_block *)(ablock->options_block); + +HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as server\n", + gsasl_prop_code_to_name(prop), ablock->name, ablock->public_name); + +for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; +expand_nmax = 0; + +switch (prop) + { + case GSASL_VALIDATE_SIMPLE: + /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */ + set_exim_authvar_from_prop(sctx, GSASL_AUTHID); + set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); + set_exim_authvar_from_prop(sctx, GSASL_PASSWORD); + + cbrc = condition_check(ablock, US"server_condition", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_VALIDATE_EXTERNAL: + if (!ablock->server_condition) + { + HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL\n"); + cbrc = GSASL_AUTHENTICATION_ERROR; + break; + } + set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); + + cbrc = condition_check(ablock, + US"server_condition (EXTERNAL)", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_VALIDATE_ANONYMOUS: + if (!ablock->server_condition) + { + HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS\n"); + cbrc = GSASL_AUTHENTICATION_ERROR; + break; + } + set_exim_authvar_from_prop(sctx, GSASL_ANONYMOUS_TOKEN); + + cbrc = condition_check(ablock, + US"server_condition (ANONYMOUS)", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_VALIDATE_GSSAPI: + /* GSASL_AUTHZID and GSASL_GSSAPI_DISPLAY_NAME + The display-name is authenticated as part of GSS, the authzid is claimed + by the SASL integration after authentication; protected against tampering + (if the SASL mechanism supports that, which Kerberos does) but is + unverified, same as normal for other mechanisms. + First coding, we had these values swapped, but for consistency and prior + to the first release of Exim with this authenticator, they've been + switched to match the ordering of GSASL_VALIDATE_SIMPLE. */ + + set_exim_authvar_from_prop(sctx, GSASL_GSSAPI_DISPLAY_NAME); + set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); + + /* In this one case, it perhaps makes sense to default back open? + But for consistency, let's just mandate server_condition here too. */ + + cbrc = condition_check(ablock, + US"server_condition (GSSAPI family)", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_SCRAM_ITER: + cbrc = prop_from_option(sctx, prop, ob->server_scram_iter); + break; + + case GSASL_SCRAM_SALT: + cbrc = prop_from_option(sctx, prop, ob->server_scram_salt); + break; + +#ifdef EXIM_GSASL_SCRAM_S_KEY + case GSASL_SCRAM_STOREDKEY: + cbrc = prop_from_option(sctx, prop, ob->server_s_key); + break; + + case GSASL_SCRAM_SERVERKEY: + cbrc = prop_from_option(sctx, prop, ob->server_key); + break; +#endif + + case GSASL_PASSWORD: + /* SCRAM-*: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM + DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM + CRAM-MD5: GSASL_AUTHID + PLAIN: GSASL_AUTHID and GSASL_AUTHZID + LOGIN: GSASL_AUTHID + */ + set_exim_authvars_from_a_az_r_props(sctx); + + if (!(s = ob->server_password)) + { + HDEBUG(D_auth) debug_printf("option not set\n"); + break; + } + if (!(tmps = CS expand_string(s))) + { + sasl_error_should_defer = !f.expand_string_forcedfail; + HDEBUG(D_auth) debug_printf("server_password expansion failed, so " + "can't tell GNU SASL library the password for %s\n", auth_vars[0]); + return GSASL_AUTHENTICATION_ERROR; + } + HDEBUG(D_auth) debug_printf(" set\n"); + gsasl_property_set(sctx, GSASL_PASSWORD, tmps); + + /* This is inadequate; don't think Exim's store stacks are geared + for memory wiping, so expanding strings will leave stuff laying around. + But no need to compound the problem, so get rid of the one we can. */ + + if (US tmps != s) memset(tmps, '\0', strlen(tmps)); + cbrc = GSASL_OK; + break; + + default: + HDEBUG(D_auth) debug_printf(" Unrecognised callback: %d\n", prop); + cbrc = GSASL_NO_CALLBACK; + } + +HDEBUG(D_auth) debug_printf("Returning %s (%s)\n", + gsasl_strerror_name(cbrc), gsasl_strerror(cbrc)); + +return cbrc; +} + + +/******************************************************************************/ + +#define PROP_OPTIONAL BIT(0) + +static BOOL +set_client_prop(Gsasl_session * sctx, Gsasl_property prop, uschar * val, + unsigned flags, uschar * buffer, int buffsize) +{ +uschar * s; + +if (!val) return !!(flags & PROP_OPTIONAL); +if (!(s = expand_string(val)) || !(flags & PROP_OPTIONAL) && !*s) + { + string_format(buffer, buffsize, "%s", expand_string_message); + return FALSE; + } +if (*s) + { + HDEBUG(D_auth) debug_printf("%s: set %s = '%s'\n", __FUNCTION__, + gsasl_prop_code_to_name(prop), s); + gsasl_property_set(sctx, prop, CS s); + } + +return TRUE; +} + +/************************************************* +* Client entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_gsasl_client( + auth_instance *ablock, /* authenticator block */ + void * sx, /* connection */ + int timeout, /* command timeout */ + uschar *buffer, /* buffer for reading response */ + int buffsize) /* size of buffer */ +{ +auth_gsasl_options_block *ob = + (auth_gsasl_options_block *)(ablock->options_block); +Gsasl_session * sctx = NULL; +struct callback_exim_state cb_state; +uschar * s; +BOOL initial = TRUE; +int rc, yield = FAIL; + +HDEBUG(D_auth) + debug_printf("GNU SASL: initialising session for %s, mechanism %s\n", + ablock->name, ob->server_mech); + +*buffer = 0; + +#ifndef DISABLE_TLS +if (tls_out.channelbinding && ob->client_channelbinding) + { +# ifndef DISABLE_TLS_RESUME + if (!tls_out.ext_master_secret && tls_out.resumption == RESUME_USED) + { /* Per RFC 7677 section 4. See also RFC 7627, "Triple Handshake" + vulnerability, and https://www.mitls.org/pages/attacks/3SHAKE */ + string_format(buffer, buffsize, "%s", + "channel binding not usable on resumed TLS without extended-master-secret"); + return FAIL; + } +# endif +# ifdef CHANNELBIND_HACK + /* This is a gross hack to get around the library before 1.9.2 + a) requiring that c-b was already set, at the _start() call, and + b) caching a b64'd version of the binding then which it never updates. */ + + gsasl_callback_hook_set(gsasl_ctx, tls_out.channelbinding); +# endif + } +#endif + +if ((rc = gsasl_client_start(gsasl_ctx, CCS ob->server_mech, &sctx)) != GSASL_OK) + { + string_format(buffer, buffsize, "GNU SASL: session start failure: %s (%s)", + gsasl_strerror_name(rc), gsasl_strerror(rc)); + HDEBUG(D_auth) debug_printf("%s\n", buffer); + return ERROR; + } + +cb_state.ablock = ablock; +cb_state.currently = CURRENTLY_CLIENT; +gsasl_session_hook_set(sctx, &cb_state); + +/* Set properties */ + +if ( !set_client_prop(sctx, GSASL_PASSWORD, ob->client_password, + 0, buffer, buffsize) + || !set_client_prop(sctx, GSASL_AUTHID, ob->client_username, + 0, buffer, buffsize) + || !set_client_prop(sctx, GSASL_AUTHZID, ob->client_authz, + PROP_OPTIONAL, buffer, buffsize) + ) + return ERROR; + +#ifndef DISABLE_TLS +if (tls_out.channelbinding) + if (ob->client_channelbinding) + { + HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n", + ablock->name); +# ifndef CHANNELBIND_HACK + gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding); +# endif + } + else + HDEBUG(D_auth) + debug_printf("Auth %s: Not enabling channel-binding (data available)\n", + ablock->name); +#endif + +/* Run the SASL conversation with the server */ + +for(s = NULL; ;) + { + uschar * outstr; + BOOL fail = TRUE; + + rc = gsasl_step64(sctx, CS s, CSS &outstr); + + if (rc == GSASL_NEEDS_MORE || rc == GSASL_OK) + { + fail = initial + ? smtp_write_command(sx, SCMD_FLUSH, + outstr ? "AUTH %s %s\r\n" : "AUTH %s\r\n", + ablock->public_name, outstr) <= 0 + : outstr + ? smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", outstr) <= 0 + : FALSE; + free(outstr); + if (fail) + { + yield = FAIL_SEND; + goto done; + } + initial = FALSE; + } + + if (rc != GSASL_NEEDS_MORE) + { + if (rc != GSASL_OK) + { + string_format(buffer, buffsize, "gsasl: %s", gsasl_strerror(rc)); + break; + } + + /* expecting a final 2xx from the server, accepting the AUTH */ + + if (smtp_read_response(sx, buffer, buffsize, '2', timeout)) + yield = OK; + break; /* from SASL sequence loop */ + } + + /* 2xx or 3xx response is acceptable. If 2xx, no further input */ + + if (!smtp_read_response(sx, buffer, buffsize, '3', timeout)) + if (errno == 0 && buffer[0] == '2') + buffer[4] = '\0'; + else + { + yield = FAIL; + goto done; + } + s = buffer + 4; + } + +done: +if (yield == OK) + { + expand_nmax = 0; + set_exim_authvar_from_prop(sctx, GSASL_AUTHID); + set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER); + set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT); + set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD); + } + +gsasl_finish(sctx); +return yield; +} + +static int +client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock) +{ +HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as client\n", + gsasl_prop_code_to_name(prop), ablock->name, ablock->public_name); +switch (prop) + { + case GSASL_CB_TLS_UNIQUE: /*XXX should never get called for this */ + HDEBUG(D_auth) + debug_printf(" filling in\n"); + gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding); + return GSASL_OK; + case GSASL_SCRAM_SALTED_PASSWORD: + { + uschar * client_spassword = + ((auth_gsasl_options_block *) ablock->options_block)->client_spassword; + uschar dummy[4]; + HDEBUG(D_auth) if (!client_spassword) + debug_printf(" client_spassword option unset\n"); + if (client_spassword) + { + expand_nmax = 0; + set_exim_authvar_from_prop(sctx, GSASL_AUTHID); + set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER); + set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT); + set_client_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD, client_spassword, + 0, dummy, sizeof(dummy)); + for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; + expand_nmax = 0; + } + break; + } + default: + HDEBUG(D_auth) + debug_printf(" not providing one\n"); + break; + } +return GSASL_NO_CALLBACK; +} + +/************************************************* +* Diagnostic API * +*************************************************/ + +gstring * +auth_gsasl_version_report(gstring * g) +{ +return string_fmt_append(g, "Library version: GNU SASL: Compile: %s\n" + " Runtime: %s\n", + GSASL_VERSION, gsasl_check_version(NULL)); +} + + + +/* Dummy */ +void auth_gsasl_macros(void) {} + +#endif /*!MACRO_PREDEF*/ +#endif /* AUTH_GSASL */ + +/* End of gsasl_exim.c */ diff --git a/src/auths/gsasl_exim.h b/src/auths/gsasl_exim.h new file mode 100644 index 0000000..19c9036 --- /dev/null +++ b/src/auths/gsasl_exim.h @@ -0,0 +1,53 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2019 - 2022 */ +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Copyright (c) Twitter Inc 2012 */ + +/* Interface to GNU SASL library for generic authentication. */ + +/* Authenticator-specific options. */ + +typedef struct { + uschar *server_service; + uschar *server_hostname; + uschar *server_realm; + uschar *server_mech; + uschar *server_password; + uschar *server_key; + uschar *server_s_key; + uschar *server_scram_iter; + uschar *server_scram_salt; + + uschar *client_username; + uschar *client_password; + uschar *client_authz; + uschar *client_spassword; + + BOOL server_channelbinding; + BOOL client_channelbinding; +} auth_gsasl_options_block; + +/* Data for reading the authenticator-specific options. */ + +extern optionlist auth_gsasl_options[]; +extern int auth_gsasl_options_count; + +/* Defaults for the authenticator-specific options. */ + +extern auth_gsasl_options_block auth_gsasl_option_defaults; + +/* The entry points for the mechanism */ + +extern void auth_gsasl_init(auth_instance *); +extern int auth_gsasl_server(auth_instance *, uschar *); +extern int auth_gsasl_client(auth_instance *, void *, + int, uschar *, int); +extern gstring * auth_gsasl_version_report(gstring *); +extern void auth_gsasl_macros(void); + +/* End of gsasl_exim.h */ diff --git a/src/auths/heimdal_gssapi.c b/src/auths/heimdal_gssapi.c new file mode 100644 index 0000000..3817632 --- /dev/null +++ b/src/auths/heimdal_gssapi.c @@ -0,0 +1,618 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2020 - 2022 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Copyright (c) Twitter Inc 2012 + Author: Phil Pennock <pdp@exim.org> */ +/* Copyright (c) Phil Pennock 2012 */ + +/* Interface to Heimdal library for GSSAPI authentication. */ + +/* Naming and rationale + +Sensibly, this integration would be deferred to a SASL library, but none +of them appear to offer keytab file selection interfaces in their APIs. It +might be that this driver only requires minor modification to work with MIT +Kerberos. + +Heimdal provides a number of interfaces for various forms of authentication. +As GS2 does not appear to provide keytab control interfaces either, we may +end up supporting that too. It's possible that we could trivially expand to +support NTLM support via Heimdal, etc. Rather than try to be too generic +immediately, this driver is directly only supporting GSSAPI. + +Without rename, we could add an option for GS2 support in the future. +*/ + +/* Sources + +* mailcheck-imap (Perl, client-side, written by me years ago) +* gsasl driver (GPL, server-side) +* heimdal sources and man-pages, plus http://www.h5l.org/manual/ +* FreeBSD man-pages (very informative!) +* http://www.ggf.org/documents/GFD.24.pdf confirming GSS_KRB5_REGISTER_ACCEPTOR_IDENTITY_X + semantics, that found by browsing Heimdal source to find how to set the keytab; however, + after multiple attempts I failed to get that to work and instead switched to + gsskrb5_register_acceptor_identity(). +*/ + +#include "../exim.h" + +#ifndef AUTH_HEIMDAL_GSSAPI +/* dummy function to satisfy compilers when we link in an "empty" file. */ +static void dummy(int x); +static void dummy2(int x) { dummy(x-1); } +static void dummy(int x) { dummy2(x-1); } +#else + +#include <gssapi/gssapi.h> +#include <gssapi/gssapi_krb5.h> + +/* for the _init debugging */ +#include <krb5.h> + +#include "heimdal_gssapi.h" + +/* Authenticator-specific options. */ +optionlist auth_heimdal_gssapi_options[] = { + { "server_hostname", opt_stringptr, + OPT_OFF(auth_heimdal_gssapi_options_block, server_hostname) }, + { "server_keytab", opt_stringptr, + OPT_OFF(auth_heimdal_gssapi_options_block, server_keytab) }, + { "server_service", opt_stringptr, + OPT_OFF(auth_heimdal_gssapi_options_block, server_service) } +}; + +int auth_heimdal_gssapi_options_count = + sizeof(auth_heimdal_gssapi_options)/sizeof(optionlist); + +/* Defaults for the authenticator-specific options. */ +auth_heimdal_gssapi_options_block auth_heimdal_gssapi_option_defaults = { + US"$primary_hostname", /* server_hostname */ + NULL, /* server_keytab */ + US"smtp", /* server_service */ +}; + + +#ifdef MACRO_PREDEF + +/* Dummy values */ +void auth_heimdal_gssapi_init(auth_instance *ablock) {} +int auth_heimdal_gssapi_server(auth_instance *ablock, uschar *data) {return 0;} +int auth_heimdal_gssapi_client(auth_instance *ablock, void * sx, + int timeout, uschar *buffer, int buffsize) {return 0;} +gstring * auth_heimdal_gssapi_version_report(gstring * g) {} + +#else /*!MACRO_PREDEF*/ + + + +/* "Globals" for managing the heimdal_gssapi interface. */ + +/* Utility functions */ +static void + exim_heimdal_error_debug(const char *, krb5_context, krb5_error_code); +static int + exim_gssapi_error_defer(rmark, OM_uint32, OM_uint32, const char *, ...) + PRINTF_FUNCTION(4, 5); + +#define EmptyBuf(buf) do { buf.value = NULL; buf.length = 0; } while (0) + + +/************************************************* +* Initialization entry point * +*************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + +/* Heimdal provides a GSSAPI extension method for setting the keytab; +in the init, we mostly just use raw krb5 methods so that we can report +the keytab contents, for -D+auth debugging. */ + +void +auth_heimdal_gssapi_init(auth_instance *ablock) +{ +krb5_context context; +krb5_keytab keytab; +krb5_kt_cursor cursor; +krb5_keytab_entry entry; +krb5_error_code krc; +char *principal, *enctype_s; +const char *k_keytab_typed_name = NULL; +auth_heimdal_gssapi_options_block *ob = + (auth_heimdal_gssapi_options_block *)(ablock->options_block); + +ablock->server = FALSE; +ablock->client = FALSE; + +if (!ob->server_service || !*ob->server_service) + { + HDEBUG(D_auth) debug_printf("heimdal: missing server_service\n"); + return; + } + +if ((krc = krb5_init_context(&context))) + { + int kerr = errno; + HDEBUG(D_auth) debug_printf("heimdal: failed to initialise krb5 context: %s\n", + strerror(kerr)); + return; + } + +if (ob->server_keytab) + { + k_keytab_typed_name = CCS string_sprintf("file:%s", expand_string(ob->server_keytab)); + HDEBUG(D_auth) debug_printf("heimdal: using keytab %s\n", k_keytab_typed_name); + if ((krc = krb5_kt_resolve(context, k_keytab_typed_name, &keytab))) + { + HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_resolve", context, krc); + return; + } + } +else + { + HDEBUG(D_auth) debug_printf("heimdal: using system default keytab\n"); + if ((krc = krb5_kt_default(context, &keytab))) + { + HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_default", context, krc); + return; + } + } + +HDEBUG(D_auth) + { + /* http://www.h5l.org/manual/HEAD/krb5/krb5_keytab_intro.html */ + if ((krc = krb5_kt_start_seq_get(context, keytab, &cursor))) + exim_heimdal_error_debug("krb5_kt_start_seq_get", context, krc); + else + { + while (!(krc = krb5_kt_next_entry(context, keytab, &entry, &cursor))) + { + principal = enctype_s = NULL; + krb5_unparse_name(context, entry.principal, &principal); + krb5_enctype_to_string(context, entry.keyblock.keytype, &enctype_s); + debug_printf("heimdal: keytab principal: %s vno=%d type=%s\n", + principal ? principal : "??", + entry.vno, + enctype_s ? enctype_s : "??"); + free(principal); + free(enctype_s); + krb5_kt_free_entry(context, &entry); + } + if ((krc = krb5_kt_end_seq_get(context, keytab, &cursor))) + exim_heimdal_error_debug("krb5_kt_end_seq_get", context, krc); + } + } + +if ((krc = krb5_kt_close(context, keytab))) + HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_close", context, krc); + +krb5_free_context(context); + +ablock->server = TRUE; +} + + +static void +exim_heimdal_error_debug(const char *label, + krb5_context context, krb5_error_code err) +{ +const char *kerrsc; +kerrsc = krb5_get_error_message(context, err); +debug_printf("heimdal %s: %s\n", label, kerrsc ? kerrsc : "unknown error"); +krb5_free_error_message(context, kerrsc); +} + +/************************************************* +* Server entry point * +*************************************************/ + +/* For interface, see auths/README */ + +/* GSSAPI notes: +OM_uint32: portable type for unsigned int32 +gss_buffer_desc / *gss_buffer_t: hold/point-to size_t .length & void *value + -- all strings/etc passed in should go through one of these + -- when allocated by gssapi, release with gss_release_buffer() +*/ + +int +auth_heimdal_gssapi_server(auth_instance *ablock, uschar *initial_data) +{ +gss_name_t gclient = GSS_C_NO_NAME; +gss_name_t gserver = GSS_C_NO_NAME; +gss_cred_id_t gcred = GSS_C_NO_CREDENTIAL; +gss_ctx_id_t gcontext = GSS_C_NO_CONTEXT; +uschar *ex_server_str; +gss_buffer_desc gbufdesc = GSS_C_EMPTY_BUFFER; +gss_buffer_desc gbufdesc_in = GSS_C_EMPTY_BUFFER; +gss_buffer_desc gbufdesc_out = GSS_C_EMPTY_BUFFER; +gss_OID mech_type; +OM_uint32 maj_stat, min_stat; +int step, error_out; +uschar *tmp1, *tmp2, *from_client; +auth_heimdal_gssapi_options_block *ob = + (auth_heimdal_gssapi_options_block *)(ablock->options_block); +BOOL handled_empty_ir; +rmark store_reset_point; +uschar *keytab; +uschar sasl_config[4]; +uschar requested_qop; + +store_reset_point = store_mark(); + +HDEBUG(D_auth) + debug_printf("heimdal: initialising auth context for %s\n", ablock->name); + +/* Construct our gss_name_t gserver describing ourselves */ +tmp1 = expand_string(ob->server_service); +tmp2 = expand_string(ob->server_hostname); +ex_server_str = string_sprintf("%s@%s", tmp1, tmp2); +gbufdesc.value = (void *) ex_server_str; +gbufdesc.length = Ustrlen(ex_server_str); +maj_stat = gss_import_name(&min_stat, + &gbufdesc, GSS_C_NT_HOSTBASED_SERVICE, &gserver); +if (GSS_ERROR(maj_stat)) + return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat, + "gss_import_name(%s)", CS gbufdesc.value); + +/* Use a specific keytab, if specified */ +if (ob->server_keytab) + { + keytab = expand_string(ob->server_keytab); + maj_stat = gsskrb5_register_acceptor_identity(CCS keytab); + if (GSS_ERROR(maj_stat)) + return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat, + "registering keytab \"%s\"", keytab); + HDEBUG(D_auth) + debug_printf("heimdal: using keytab \"%s\"\n", keytab); + } + +/* Acquire our credentials */ +maj_stat = gss_acquire_cred(&min_stat, + gserver, /* desired name */ + 0, /* time */ + GSS_C_NULL_OID_SET, /* desired mechs */ + GSS_C_ACCEPT, /* cred usage */ + &gcred, /* handle */ + NULL /* actual mechs */, + NULL /* time rec */); +if (GSS_ERROR(maj_stat)) + return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat, + "gss_acquire_cred(%s)", ex_server_str); + +maj_stat = gss_release_name(&min_stat, &gserver); + +HDEBUG(D_auth) debug_printf("heimdal: have server credentials.\n"); + +/* Loop talking to client */ +step = 0; +from_client = initial_data; +handled_empty_ir = FALSE; +error_out = OK; + +/* buffer sizes: auth_get_data() uses big_buffer, which we grow per +GSSAPI RFC in _init, if needed, to meet the SHOULD size of 64KB. +(big_buffer starts life at the MUST size of 16KB). */ + +/* step values +0: getting initial data from client to feed into GSSAPI +1: iterating for as long as GSS_S_CONTINUE_NEEDED +2: GSS_S_COMPLETE, SASL wrapping for authz and qop to send to client +3: unpick final auth message from client +4: break/finish (non-step) +*/ +while (step < 4) + switch (step) + { + case 0: + if (!from_client || !*from_client) + { + if (handled_empty_ir) + { + HDEBUG(D_auth) debug_printf("gssapi: repeated empty input, grr.\n"); + error_out = BAD64; + goto ERROR_OUT; + } + + HDEBUG(D_auth) debug_printf("gssapi: missing initial response, nudging.\n"); + if ((error_out = auth_get_data(&from_client, US"", 0)) != OK) + goto ERROR_OUT; + handled_empty_ir = TRUE; + continue; + } + /* We should now have the opening data from the client, base64-encoded. */ + step += 1; + HDEBUG(D_auth) debug_printf("heimdal: have initial client data\n"); + break; + + case 1: + gbufdesc_in.length = b64decode(from_client, USS &gbufdesc_in.value); + if (gclient) + { + maj_stat = gss_release_name(&min_stat, &gclient); + gclient = GSS_C_NO_NAME; + } + maj_stat = gss_accept_sec_context(&min_stat, + &gcontext, /* context handle */ + gcred, /* acceptor cred handle */ + &gbufdesc_in, /* input from client */ + GSS_C_NO_CHANNEL_BINDINGS, /* XXX fixme: use the channel bindings from GnuTLS */ + &gclient, /* client identifier */ + &mech_type, /* mechanism in use */ + &gbufdesc_out, /* output to send to client */ + NULL, /* return flags */ + NULL, /* time rec */ + NULL /* delegated cred_handle */ + ); + if (GSS_ERROR(maj_stat)) + { + exim_gssapi_error_defer(NULL, maj_stat, min_stat, + "gss_accept_sec_context()"); + error_out = FAIL; + goto ERROR_OUT; + } + if (gbufdesc_out.length != 0) + { + error_out = auth_get_data(&from_client, + gbufdesc_out.value, gbufdesc_out.length); + if (error_out != OK) + goto ERROR_OUT; + + gss_release_buffer(&min_stat, &gbufdesc_out); + EmptyBuf(gbufdesc_out); + } + if (maj_stat == GSS_S_COMPLETE) + { + step += 1; + HDEBUG(D_auth) debug_printf("heimdal: GSS complete\n"); + } + else + HDEBUG(D_auth) debug_printf("heimdal: need more data\n"); + break; + + case 2: + memset(sasl_config, 0xFF, 4); + /* draft-ietf-sasl-gssapi-06.txt defines bitmasks for first octet + 0x01 No security layer + 0x02 Integrity protection + 0x04 Confidentiality protection + + The remaining three octets are the maximum buffer size for wrapped + content. */ + sasl_config[0] = 0x01; /* Exim does not wrap/unwrap SASL layers after auth */ + gbufdesc.value = (void *) sasl_config; + gbufdesc.length = 4; + maj_stat = gss_wrap(&min_stat, + gcontext, + 0, /* conf_req_flag: integrity only */ + GSS_C_QOP_DEFAULT, /* qop requested */ + &gbufdesc, /* message to protect */ + NULL, /* conf_state: no confidentiality applied */ + &gbufdesc_out /* output buffer */ + ); + if (GSS_ERROR(maj_stat)) + { + exim_gssapi_error_defer(NULL, maj_stat, min_stat, + "gss_wrap(SASL state after auth)"); + error_out = FAIL; + goto ERROR_OUT; + } + + HDEBUG(D_auth) debug_printf("heimdal SASL: requesting QOP with no security layers\n"); + + error_out = auth_get_data(&from_client, + gbufdesc_out.value, gbufdesc_out.length); + if (error_out != OK) + goto ERROR_OUT; + + gss_release_buffer(&min_stat, &gbufdesc_out); + EmptyBuf(gbufdesc_out); + step += 1; + break; + + case 3: + gbufdesc_in.length = b64decode(from_client, USS &gbufdesc_in.value); + maj_stat = gss_unwrap(&min_stat, + gcontext, + &gbufdesc_in, /* data from client */ + &gbufdesc_out, /* results */ + NULL, /* conf state */ + NULL /* qop state */ + ); + if (GSS_ERROR(maj_stat)) + { + exim_gssapi_error_defer(NULL, maj_stat, min_stat, + "gss_unwrap(final SASL message from client)"); + error_out = FAIL; + goto ERROR_OUT; + } + if (gbufdesc_out.length < 4) + { + HDEBUG(D_auth) + debug_printf("gssapi: final message too short; " + "need flags, buf sizes and optional authzid\n"); + error_out = FAIL; + goto ERROR_OUT; + } + + requested_qop = (CS gbufdesc_out.value)[0]; + if (!(requested_qop & 0x01)) + { + HDEBUG(D_auth) + debug_printf("gssapi: client requested security layers (%x)\n", + (unsigned int) requested_qop); + error_out = FAIL; + goto ERROR_OUT; + } + + for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; + expand_nmax = 0; + + /* Identifiers: + The SASL provided identifier is an unverified authzid. + GSSAPI provides us with a verified identifier, but it might be empty + for some clients. + */ + + /* $auth2 is authzid requested at SASL layer */ + if (gbufdesc_out.length > 4) + { + expand_nlength[2] = gbufdesc_out.length - 4; + auth_vars[1] = expand_nstring[2] = + string_copyn((US gbufdesc_out.value) + 4, expand_nlength[2]); + expand_nmax = 2; + } + + gss_release_buffer(&min_stat, &gbufdesc_out); + EmptyBuf(gbufdesc_out); + + /* $auth1 is GSSAPI display name */ + maj_stat = gss_display_name(&min_stat, + gclient, &gbufdesc_out, &mech_type); + if (GSS_ERROR(maj_stat)) + { + auth_vars[1] = expand_nstring[2] = NULL; + expand_nmax = 0; + exim_gssapi_error_defer(NULL, maj_stat, min_stat, + "gss_display_name(client identifier)"); + error_out = FAIL; + goto ERROR_OUT; + } + + expand_nlength[1] = gbufdesc_out.length; + auth_vars[0] = expand_nstring[1] = + string_copyn(gbufdesc_out.value, gbufdesc_out.length); + + if (expand_nmax == 0) /* should be: authzid was empty */ + { + expand_nmax = 2; + expand_nlength[2] = expand_nlength[1]; + auth_vars[1] = expand_nstring[2] = string_copyn(expand_nstring[1], expand_nlength[1]); + HDEBUG(D_auth) + debug_printf("heimdal SASL: empty authzid, set to dup of GSSAPI display name\n"); + } + + HDEBUG(D_auth) + debug_printf("heimdal SASL: happy with client request\n" + " auth1 (verified GSSAPI display-name): \"%s\"\n" + " auth2 (unverified SASL requested authzid): \"%s\"\n", + auth_vars[0], auth_vars[1]); + + step += 1; + break; + + } /* switch */ + /* while step */ + + +ERROR_OUT: +maj_stat = gss_release_cred(&min_stat, &gcred); +if (gclient) + { + gss_release_name(&min_stat, &gclient); + gclient = GSS_C_NO_NAME; + } +if (gbufdesc_out.length) + { + gss_release_buffer(&min_stat, &gbufdesc_out); + EmptyBuf(gbufdesc_out); + } +if (gcontext != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&min_stat, &gcontext, GSS_C_NO_BUFFER); + +store_reset(store_reset_point); + +if (error_out != OK) + return error_out; + +/* Auth succeeded, check server_condition */ +return auth_check_serv_cond(ablock); +} + + +static int +exim_gssapi_error_defer(rmark store_reset_point, + OM_uint32 major, OM_uint32 minor, + const char *format, ...) +{ +va_list ap; +OM_uint32 maj_stat, min_stat; +OM_uint32 msgcontext = 0; +gss_buffer_desc status_string; +gstring * g; + +HDEBUG(D_auth) + { + va_start(ap, format); + g = string_vformat(NULL, SVFMT_EXTEND|SVFMT_REBUFFER, format, ap); + va_end(ap); + } + +auth_defer_msg = NULL; + +do { + maj_stat = gss_display_status(&min_stat, + major, GSS_C_GSS_CODE, GSS_C_NO_OID, &msgcontext, &status_string); + + if (!auth_defer_msg) + auth_defer_msg = string_copy(US status_string.value); + + HDEBUG(D_auth) debug_printf("heimdal %s: %.*s\n", + string_from_gstring(g), (int)status_string.length, + CS status_string.value); + gss_release_buffer(&min_stat, &status_string); + + } while (msgcontext != 0); + +if (store_reset_point) + store_reset(store_reset_point); +return DEFER; +} + + +/************************************************* +* Client entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_heimdal_gssapi_client( + auth_instance *ablock, /* authenticator block */ + void * sx, /* connection */ + int timeout, /* command timeout */ + uschar *buffer, /* buffer for reading response */ + int buffsize) /* size of buffer */ +{ +HDEBUG(D_auth) + debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n"); +/* NOT IMPLEMENTED */ +return FAIL; +} + +/************************************************* +* Diagnostic API * +*************************************************/ + +gstring * +auth_heimdal_gssapi_version_report(gstring * g) +{ +/* No build-time constants available unless we link against libraries at +build-time and export the result as a string into a header ourselves. */ + +return string_fmt_append(g, "Library version: Heimdal: Runtime: %s\n" + " Build Info: %s\n", + heimdal_version, heimdal_long_version)); +} + +#endif /*!MACRO_PREDEF*/ +#endif /* AUTH_HEIMDAL_GSSAPI */ + +/* End of heimdal_gssapi.c */ diff --git a/src/auths/heimdal_gssapi.h b/src/auths/heimdal_gssapi.h new file mode 100644 index 0000000..49775af --- /dev/null +++ b/src/auths/heimdal_gssapi.h @@ -0,0 +1,39 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2022 */ +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Copyright (c) Twitter Inc 2012 + Author: Phil Pennock <pdp@exim.org> */ +/* Copyright (c) Phil Pennock 2012 */ + +/* Interface to Heimdal library for GSSAPI authentication. */ + +/* Authenticator-specific options. */ + +typedef struct { + uschar *server_hostname; + uschar *server_keytab; + uschar *server_service; +} auth_heimdal_gssapi_options_block; + +/* Data for reading the authenticator-specific options. */ + +extern optionlist auth_heimdal_gssapi_options[]; +extern int auth_heimdal_gssapi_options_count; + +/* Defaults for the authenticator-specific options. */ + +extern auth_heimdal_gssapi_options_block auth_heimdal_gssapi_option_defaults; + +/* The entry points for the mechanism */ + +extern void auth_heimdal_gssapi_init(auth_instance *); +extern int auth_heimdal_gssapi_server(auth_instance *, uschar *); +extern int auth_heimdal_gssapi_client(auth_instance *, void *, int, uschar *, int); +extern void auth_heimdal_gssapi_version_report(BOOL); + +/* End of heimdal_gssapi.h */ diff --git a/src/auths/plaintext.c b/src/auths/plaintext.c new file mode 100644 index 0000000..58d1783 --- /dev/null +++ b/src/auths/plaintext.c @@ -0,0 +1,179 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* Copyright (c) The Exim Maintainers 2020 - 2021 */ +/* See the file NOTICE for conditions of use and distribution. */ + +#include "../exim.h" +#include "plaintext.h" + + +/* Options specific to the plaintext authentication mechanism. */ + +optionlist auth_plaintext_options[] = { + { "client_ignore_invalid_base64", opt_bool, + OPT_OFF(auth_plaintext_options_block, client_ignore_invalid_base64) }, + { "client_send", opt_stringptr, + OPT_OFF(auth_plaintext_options_block, client_send) }, + { "server_prompts", opt_stringptr, + OPT_OFF(auth_plaintext_options_block, server_prompts) } +}; + +/* Size of the options list. An extern variable has to be used so that its +address can appear in the tables drtables.c. */ + +int auth_plaintext_options_count = + sizeof(auth_plaintext_options)/sizeof(optionlist); + +/* Default private options block for the plaintext authentication method. */ + +auth_plaintext_options_block auth_plaintext_option_defaults = { + NULL, /* server_prompts */ + NULL, /* client_send */ + FALSE /* client_ignore_invalid_base64 */ +}; + + +#ifdef MACRO_PREDEF + +/* Dummy values */ +void auth_plaintext_init(auth_instance *ablock) {} +int auth_plaintext_server(auth_instance *ablock, uschar *data) {return 0;} +int auth_plaintext_client(auth_instance *ablock, void * sx, int timeout, + uschar *buffer, int buffsize) {return 0;} + +#else /*!MACRO_PREDEF*/ + + + +/************************************************* +* Initialization entry point * +*************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + +void +auth_plaintext_init(auth_instance *ablock) +{ +auth_plaintext_options_block *ob = + (auth_plaintext_options_block *)(ablock->options_block); +if (ablock->public_name == NULL) ablock->public_name = ablock->name; +if (ablock->server_condition != NULL) ablock->server = TRUE; +if (ob->client_send != NULL) ablock->client = TRUE; +} + + + +/************************************************* +* Server entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_plaintext_server(auth_instance * ablock, uschar * data) +{ +auth_plaintext_options_block * ob = + (auth_plaintext_options_block *)(ablock->options_block); +const uschar * prompts = ob->server_prompts; +uschar * s; +int number = 1; +int rc; +int sep = 0; + +/* Expand a non-empty list of prompt strings */ + +if (prompts) + if (!(prompts = expand_cstring(prompts))) + { + auth_defer_msg = expand_string_message; + return DEFER; + } + +/* If data was supplied on the AUTH command, decode it, and split it up into +multiple items at binary zeros. The strings are put into $auth1, $auth2, etc, +up to a maximum. To retain backwards compatibility, they are also put int $1, +$2, etc. If the data consists of the string "=" it indicates a single, empty +string. */ + +if (*data) + if ((rc = auth_read_input(data)) != OK) + return rc; + +/* Now go through the list of prompt strings. Skip over any whose data has +already been provided as part of the AUTH command. For the rest, send them +out as prompts, and get a data item back. If the data item is "*", abandon the +authentication attempt. Otherwise, split it into items as above. */ + +while ( (s = string_nextinlist(&prompts, &sep, NULL, 0)) + && expand_nmax < EXPAND_MAXN) + if (number++ > expand_nmax) + if ((rc = auth_prompt(CUS s)) != OK) + return rc; + +/* We now have a number of items of data in $auth1, $auth2, etc (and also, for +compatibility, in $1, $2, etc). Authentication and authorization are handled +together for this authenticator by expanding the server_condition option. Note +that ablock->server_condition is always non-NULL because that's what configures +this authenticator as a server. */ + +return auth_check_serv_cond(ablock); +} + + + +/************************************************* +* Client entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_plaintext_client( + auth_instance *ablock, /* authenticator block */ + void * sx, /* smtp connextion */ + int timeout, /* command timeout */ + uschar *buffer, /* buffer for reading response */ + int buffsize) /* size of buffer */ +{ +auth_plaintext_options_block *ob = + (auth_plaintext_options_block *)(ablock->options_block); +const uschar * text = ob->client_send; +const uschar * s; +int sep = 0; +int auth_var_idx = 0, rc; +int flags = AUTH_ITEM_FIRST; + +if (ob->client_ignore_invalid_base64) + flags |= AUTH_ITEM_IGN64; + +/* The text is broken up into a number of different data items, which are +sent one by one. The first one is sent with the AUTH command; the remainder are +sent in response to subsequent prompts. Each is expanded before being sent. */ + +while ((s = string_nextinlist(&text, &sep, NULL, 0))) + { + if (!text) + flags |= AUTH_ITEM_LAST; + + if ((rc = auth_client_item(sx, ablock, &s, flags, timeout, buffer, buffsize)) + != DEFER) + return rc; + + flags &= ~AUTH_ITEM_FIRST; + + if (auth_var_idx < AUTH_VARS) + auth_vars[auth_var_idx++] = string_copy(s); + } + +/* Control should never actually get here. */ + +return FAIL; +} + +#endif /*!MACRO_PREDEF*/ +/* End of plaintext.c */ diff --git a/src/auths/plaintext.h b/src/auths/plaintext.h new file mode 100644 index 0000000..4c6d011 --- /dev/null +++ b/src/auths/plaintext.h @@ -0,0 +1,31 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Private structure for the private options. */ + +typedef struct { + uschar *server_prompts; + uschar *client_send; + BOOL client_ignore_invalid_base64; +} auth_plaintext_options_block; + +/* Data for reading the private options. */ + +extern optionlist auth_plaintext_options[]; +extern int auth_plaintext_options_count; + +/* Block containing default values. */ + +extern auth_plaintext_options_block auth_plaintext_option_defaults; + +/* The entry points for the mechanism */ + +extern void auth_plaintext_init(auth_instance *); +extern int auth_plaintext_server(auth_instance *, uschar *); +extern int auth_plaintext_client(auth_instance *, void *, int, uschar *, int); + +/* End of plaintext.h */ diff --git a/src/auths/pwcheck.c b/src/auths/pwcheck.c new file mode 100644 index 0000000..7dd529f --- /dev/null +++ b/src/auths/pwcheck.c @@ -0,0 +1,449 @@ +/* SASL server API implementation + * Rob Siemborski + * Tim Martin + * $Id: checkpw.c,v 1.49 2002/03/07 19:14:04 ken3 Exp $ + */ +/* Copyright (c) The Exim Maintainers 2021 - 2022 */ +/* + * Copyright (c) 2001 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Office of Technology Transfer + * Carnegie Mellon University + * 5000 Forbes Avenue + * Pittsburgh, PA 15213-3890 + * (412) 268-4387, fax: (412) 268-7395 + * tech-transfer@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Taken from Cyrus-SASL library and adapted by Alexander S. Sabourenkov + * Oct 2001 - Apr 2002: Slightly modified by Philip Hazel. + * Aug 2003: new code for saslauthd from Alexander S. Sabourenkov incorporated + * by Philip Hazel (minor mods to avoid compiler warnings) + * Oct 2006: (PH) removed redundant tests on "reply" being NULL - some were + * missing, and confused someone who was using this code for some + * other purpose. Here in Exim, "reply" is never NULL. + * + * screwdriver@lxnt.info + * + */ + +/* Originally this module supported only the pwcheck daemon, which is where its +name comes from. Nowadays it supports saslauthd as well; pwcheck is in fact +deprecated. The definitions of CYRUS_PWCHECK_SOCKET and CYRUS_SASLAUTHD_SOCKET +determine whether the facilities are actually supported or not. */ + + +#include "../exim.h" +#include "pwcheck.h" + + +#if defined(CYRUS_PWCHECK_SOCKET) || defined(CYRUS_SASLAUTHD_SOCKET) + +#include <sys/uio.h> + +static int retry_read(int, void *, unsigned ); +static int retry_writev(int, struct iovec *, int ); +static int read_string(int, uschar **); +static int write_string(int, const uschar *, int); + +#endif + + +/* A dummy function that always fails if pwcheck support is not +wanted. */ + +#ifndef CYRUS_PWCHECK_SOCKET +int pwcheck_verify_password(const char *userid, + const char *passwd, + const char **reply) +{ +*reply = "pwcheck support is not included in this Exim binary"; +return PWCHECK_FAIL; +} + + +/* This is the real function */ + +#else + + /* taken from cyrus-sasl file checkpw.c */ + /* pwcheck daemon-authenticated login */ + int pwcheck_verify_password(const char *userid, + const char *passwd, + const char **reply) + { + int s, start, r, n; + struct sockaddr_un srvaddr; + struct iovec iov[2]; + static char response[1024]; + + *reply = NULL; + + s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s == -1) { return PWCHECK_FAIL; } + + memset(CS &srvaddr, 0, sizeof(srvaddr)); + srvaddr.sun_family = AF_UNIX; + strncpy(srvaddr.sun_path, CYRUS_PWCHECK_SOCKET, sizeof(srvaddr.sun_path)); + r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr)); + if (r == -1) { + DEBUG(D_auth) + debug_printf("Cannot connect to pwcheck daemon (at '%s')\n",CYRUS_PWCHECK_SOCKET); + *reply = "cannot connect to pwcheck daemon"; + return PWCHECK_FAIL; + } + + iov[0].iov_base = CS userid; + iov[0].iov_len = strlen(userid)+1; + iov[1].iov_base = CS passwd; + iov[1].iov_len = strlen(passwd)+1; + + retry_writev(s, iov, 2); + + start = 0; + while (start < sizeof(response) - 1) { + n = read(s, response+start, sizeof(response) - 1 - start); + if (n < 1) break; + start += n; + } + + (void)close(s); + + if (start > 1 && !strncmp(response, "OK", 2)) { + return PWCHECK_OK; + } + + response[start] = '\0'; + *reply = response; + return PWCHECK_NO; + } + +#endif + + + + /* A dummy function that always fails if saslauthd support is not +wanted. */ + +#ifndef CYRUS_SASLAUTHD_SOCKET +int saslauthd_verify_password(const uschar *userid, + const uschar *passwd, + const uschar *service, + const uschar *realm, + const uschar **reply) +{ +*reply = US"saslauthd support is not included in this Exim binary"; +return PWCHECK_FAIL; +} + + +/* This is the real function */ + +#else + /* written from scratch */ + /* saslauthd daemon-authenticated login */ + +int saslauthd_verify_password(const uschar *userid, + const uschar *password, + const uschar *service, + const uschar *realm, + const uschar **reply) +{ + uschar *daemon_reply = NULL; + int s, r; + struct sockaddr_un srvaddr; + + DEBUG(D_auth) + debug_printf("saslauthd userid='%s' servicename='%s'" + " realm='%s'\n", userid, service, realm ); + + *reply = NULL; + + s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s == -1) { + *reply = CUstrerror(errno); + return PWCHECK_FAIL; + } + + memset(CS &srvaddr, 0, sizeof(srvaddr)); + srvaddr.sun_family = AF_UNIX; + strncpy(srvaddr.sun_path, CYRUS_SASLAUTHD_SOCKET, + sizeof(srvaddr.sun_path)); + r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr)); + if (r == -1) { + DEBUG(D_auth) + debug_printf("Cannot connect to saslauthd daemon (at '%s'): %s\n", + CYRUS_SASLAUTHD_SOCKET, strerror(errno)); + *reply = string_sprintf("cannot connect to saslauthd daemon at " + "%s: %s", CYRUS_SASLAUTHD_SOCKET, + strerror(errno)); + return PWCHECK_FAIL; + } + + if ( write_string(s, userid, Ustrlen(userid)) < 0) { + DEBUG(D_auth) + debug_printf("Failed to send userid to saslauthd daemon \n"); + (void)close(s); + return PWCHECK_FAIL; + } + + if ( write_string(s, password, Ustrlen(password)) < 0) { + DEBUG(D_auth) + debug_printf("Failed to send password to saslauthd daemon \n"); + (void)close(s); + return PWCHECK_FAIL; + } + + memset((void *)password, 0, Ustrlen(password)); + + if ( write_string(s, service, Ustrlen(service)) < 0) { + DEBUG(D_auth) + debug_printf("Failed to send service name to saslauthd daemon \n"); + (void)close(s); + return PWCHECK_FAIL; + } + + if ( write_string(s, realm, Ustrlen(realm)) < 0) { + DEBUG(D_auth) + debug_printf("Failed to send realm to saslauthd daemon \n"); + (void)close(s); + return PWCHECK_FAIL; + } + + if ( read_string(s, &daemon_reply ) < 2) { + DEBUG(D_auth) + debug_printf("Corrupted answer '%s' received. \n", daemon_reply); + (void)close(s); + return PWCHECK_FAIL; + } + + (void)close(s); + + DEBUG(D_auth) + debug_printf("Answer '%s' received. \n", daemon_reply); + + *reply = daemon_reply; + + if ( (daemon_reply[0] == 'O') && (daemon_reply[1] == 'K') ) + return PWCHECK_OK; + + if ( (daemon_reply[0] == 'N') && (daemon_reply[1] == 'O') ) + return PWCHECK_NO; + + return PWCHECK_FAIL; +} + +#endif + + +/* helper functions */ +#if defined(CYRUS_PWCHECK_SOCKET) || defined(CYRUS_SASLAUTHD_SOCKET) + +#define MAX_REQ_LEN 1024 + +/* written from scratch */ + +/* FUNCTION: read_string */ + +/* SYNOPSIS + * read a sasld-style counted string into + * store-allocated buffer, set pointer to the buffer, + * return number of bytes read or -1 on failure. + * END SYNOPSIS */ + +static int read_string(int fd, uschar **retval) { + unsigned short count; + int rc; + + rc = (retry_read(fd, &count, sizeof(count)) < (int) sizeof(count)); + if (!rc) { + count = ntohs(count); + if (count > MAX_REQ_LEN) { + return -1; + } else { + /* Assume the file is trusted, so no tainting */ + *retval = store_get(count + 1, GET_UNTAINTED); + rc = (retry_read(fd, *retval, count) < (int) count); + (*retval)[count] = '\0'; + return count; + } + } + return -1; +} + + +/* FUNCTION: write_string */ + +/* SYNOPSIS + * write a sasld-style counted string into given fd + * written bytes on success, -1 on failure. + * END SYNOPSIS */ + +static int write_string(int fd, const uschar *string, int len) { + unsigned short count; + int rc; + struct iovec iov[2]; + + count = htons(len); + + iov[0].iov_base = (void *) &count; + iov[0].iov_len = sizeof(count); + iov[1].iov_base = (void *) string; + iov[1].iov_len = len; + + rc = retry_writev(fd, iov, 2); + + return rc; +} + + +/* taken from cyrus-sasl file saslauthd/saslauthd-unix.c */ + +/* FUNCTION: retry_read */ + +/* SYNOPSIS + * Keep calling the read() system call with 'fd', 'buf', and 'nbyte' + * until all the data is read in or an error occurs. + * END SYNOPSIS */ +static int retry_read(int fd, void *inbuf, unsigned nbyte) +{ + int n; + int nread = 0; + char *buf = CS inbuf; + + if (nbyte == 0) return 0; + + for (;;) { + n = read(fd, buf, nbyte); + if (n == 0) { + /* end of file */ + return -1; + } + if (n == -1) { + if (errno == EINTR) continue; + return -1; + } + + nread += n; + + if (n >= (int) nbyte) return nread; + + buf += n; + nbyte -= n; + } +} + +/* END FUNCTION: retry_read */ + +/* FUNCTION: retry_writev */ + +/* SYNOPSIS + * Keep calling the writev() system call with 'fd', 'iov', and 'iovcnt' + * until all the data is written out or an error occurs. + * END SYNOPSIS */ + +static int /* R: bytes written, or -1 on error */ +retry_writev ( + /* PARAMETERS */ + int fd, /* I: fd to write on */ + struct iovec *iov, /* U: iovec array base + * modified as data written */ + int iovcnt /* I: number of iovec entries */ + /* END PARAMETERS */ + ) +{ + /* VARIABLES */ + int n; /* return value from writev() */ + int i; /* loop counter */ + int written; /* bytes written so far */ + static int iov_max; /* max number of iovec entries */ + /* END VARIABLES */ + + /* initialization */ +#ifdef MAXIOV + iov_max = MAXIOV; +#else /* ! MAXIOV */ +# ifdef IOV_MAX + iov_max = IOV_MAX; +# else /* ! IOV_MAX */ + iov_max = 8192; +# endif /* ! IOV_MAX */ +#endif /* ! MAXIOV */ + written = 0; + + for (;;) { + + while (iovcnt && iov[0].iov_len == 0) { + iov++; + iovcnt--; + } + + if (!iovcnt) { + return written; + } + + n = writev(fd, iov, iovcnt > iov_max ? iov_max : iovcnt); + if (n == -1) { + if (errno == EINVAL && iov_max > 10) { + iov_max /= 2; + continue; + } + if (errno == EINTR) { + continue; + } + return -1; + } else { + written += n; + } + + for (i = 0; i < iovcnt; i++) { + if (iov[i].iov_len > (unsigned) n) { + iov[i].iov_base = CS iov[i].iov_base + n; + iov[i].iov_len -= n; + break; + } + n -= iov[i].iov_len; + iov[i].iov_len = 0; + } + + if (i == iovcnt) { + return written; + } + } + /* NOTREACHED */ +} + +/* END FUNCTION: retry_writev */ +#endif + +/* End of auths/pwcheck.c */ diff --git a/src/auths/pwcheck.h b/src/auths/pwcheck.h new file mode 100644 index 0000000..1287ea2 --- /dev/null +++ b/src/auths/pwcheck.h @@ -0,0 +1,27 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* This file provides support for authentication via the Cyrus SASL pwcheck +daemon (whence its name) and the newer saslauthd daemon. */ + +/* Error codes used internally within the authentication functions */ + +/* PWCHECK_OK - auth successful + PWCHECK_NO - access denied + PWCHECK_FAIL - [temporary] failure */ + +#define PWCHECK_OK 0 +#define PWCHECK_NO 1 +#define PWCHECK_FAIL 2 + +/* Cyrus functions for doing the business. */ + +extern int pwcheck_verify_password(const char *, const char *, const char **); +extern int saslauthd_verify_password(const uschar *, const uschar *, + const uschar *, const uschar *, const uschar **); + +/* End of pwcheck.h */ diff --git a/src/auths/spa.c b/src/auths/spa.c new file mode 100644 index 0000000..ff90d33 --- /dev/null +++ b/src/auths/spa.c @@ -0,0 +1,376 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* Copyright (c) The Exim Maintainers 2020 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* This file, which provides support for Microsoft's Secure Password +Authentication, was contributed by Marc Prud'hommeaux. Tom Kistner added SPA +server support. I (PH) have only modified it in very trivial ways. + +References: + http://www.innovation.ch/java/ntlm.html + http://www.kuro5hin.org/story/2002/4/28/1436/66154 + http://download.microsoft.com/download/9/5/e/95ef66af-9026-4bb0-a41d-a4f81802d92c/%5bMS-SMTP%5d.pdf + + * It seems that some systems have existing but different definitions of some + * of the following types. I received a complaint about "int16" causing + * compilation problems. So I (PH) have renamed them all, to be on the safe + * side, by adding 'x' on the end. See auths/auth-spa.h. + + * typedef signed short int16; + * typedef unsigned short uint16; + * typedef unsigned uint32; + * typedef unsigned char uint8; + +07-August-2003: PH: Patched up the code to avoid assert bombouts for stupid + input data. Find appropriate comment by grepping for "PH". +16-October-2006: PH: Added a call to auth_check_serv_cond() at the end +05-June-2010: PP: handle SASL initial response +*/ + + +#include "../exim.h" +#include "spa.h" + +/* #define DEBUG_SPA */ + +#ifdef DEBUG_SPA +#define DSPA(x,y,z) debug_printf(x,y,z) +#else +#define DSPA(x,y,z) +#endif + +/* Options specific to the spa authentication mechanism. */ + +optionlist auth_spa_options[] = { + { "client_domain", opt_stringptr, + OPT_OFF(auth_spa_options_block, spa_domain) }, + { "client_password", opt_stringptr, + OPT_OFF(auth_spa_options_block, spa_password) }, + { "client_username", opt_stringptr, + OPT_OFF(auth_spa_options_block, spa_username) }, + { "server_password", opt_stringptr, + OPT_OFF(auth_spa_options_block, spa_serverpassword) } +}; + +/* Size of the options list. An extern variable has to be used so that its +address can appear in the tables drtables.c. */ + +int auth_spa_options_count = + sizeof(auth_spa_options)/sizeof(optionlist); + +/* Default private options block for the condition authentication method. */ + +auth_spa_options_block auth_spa_option_defaults = { + NULL, /* spa_password */ + NULL, /* spa_username */ + NULL, /* spa_domain */ + NULL /* spa_serverpassword (for server side use) */ +}; + + +#ifdef MACRO_PREDEF + +/* Dummy values */ +void auth_spa_init(auth_instance *ablock) {} +int auth_spa_server(auth_instance *ablock, uschar *data) {return 0;} +int auth_spa_client(auth_instance *ablock, void * sx, int timeout, + uschar *buffer, int buffsize) {return 0;} + +#else /*!MACRO_PREDEF*/ + + + + +/************************************************* +* Initialization entry point * +*************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + +void +auth_spa_init(auth_instance *ablock) +{ +auth_spa_options_block *ob = + (auth_spa_options_block *)(ablock->options_block); + +/* The public name defaults to the authenticator name */ + +if (ablock->public_name == NULL) ablock->public_name = ablock->name; + +/* Both username and password must be set for a client */ + +if ((ob->spa_username == NULL) != (ob->spa_password == NULL)) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:\n " + "one of client_username and client_password cannot be set without " + "the other", ablock->name); +ablock->client = ob->spa_username != NULL; + +/* For a server we have just one option */ + +ablock->server = ob->spa_serverpassword != NULL; +} + + + +/************************************************* +* Server entry point * +*************************************************/ + +/* For interface, see auths/README */ + +#define CVAL(buf,pos) ((US (buf))[pos]) +#define PVAL(buf,pos) ((unsigned)CVAL(buf,pos)) +#define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8) +#define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16) + +int +auth_spa_server(auth_instance *ablock, uschar *data) +{ +auth_spa_options_block *ob = (auth_spa_options_block *)(ablock->options_block); +uint8x lmRespData[24]; +uint8x ntRespData[24]; +SPAAuthRequest request; +SPAAuthChallenge challenge; +SPAAuthResponse response; +SPAAuthResponse *responseptr = &response; +uschar msgbuf[2048]; +uschar *clearpass, *s; +unsigned off; + +/* send a 334, MS Exchange style, and grab the client's request, +unless we already have it via an initial response. */ + +if (!*data && auth_get_no64_data(&data, US"NTLM supported") != OK) + return FAIL; + +if (spa_base64_to_bits(CS &request, sizeof(request), CCS data) < 0) + { + DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in " + "request: %s\n", data); + return FAIL; + } + +/* create a challenge and send it back */ + +spa_build_auth_challenge(&request, &challenge); +spa_bits_to_base64(msgbuf, US &challenge, spa_request_length(&challenge)); + +if (auth_get_no64_data(&data, msgbuf) != OK) + return FAIL; + +/* dump client response */ +if (spa_base64_to_bits(CS &response, sizeof(response), CCS data) < 0) + { + DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in " + "response: %s\n", data); + return FAIL; + } + +/*************************************************************** +PH 07-Aug-2003: The original code here was this: + +Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) + + IVAL(&responseptr->uUser.offset,0), + SVAL(&responseptr->uUser.len,0)/2) ); + +However, if the response data is too long, unicodeToString bombs out on +an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good +idea. It's too messy to try to rework that function to return an error because +it is called from a number of other places in the auth-spa.c module. Instead, +since it is a very small function, I reproduce its code here, with a size check +that causes failure if the size of msgbuf is exceeded. ****/ + + { + int i; + char * p; + int len = SVAL(&responseptr->uUser.len,0)/2; + + if ( (off = IVAL(&responseptr->uUser.offset,0)) >= sizeof(SPAAuthResponse) + || len >= sizeof(responseptr->buffer)/2 + || (p = (CS responseptr) + off) + len*2 >= CS (responseptr+1) + ) + { + DEBUG(D_auth) + debug_printf("auth_spa_server(): bad uUser spec in response\n"); + return FAIL; + } + + if (len + 1 >= sizeof(msgbuf)) return FAIL; + for (i = 0; i < len; ++i) + { + msgbuf[i] = *p & 0x7f; + p += 2; + } + msgbuf[i] = 0; + } + +/***************************************************************/ + +/* Put the username in $auth1 and $1. The former is now the preferred variable; +the latter is the original variable. These have to be out of stack memory, and +need to be available once known even if not authenticated, for error messages +(server_set_id, which only makes it to authenticated_id if we return OK) */ + +auth_vars[0] = expand_nstring[1] = string_copy(msgbuf); +expand_nlength[1] = Ustrlen(msgbuf); +expand_nmax = 1; + +debug_print_string(ablock->server_debug_string); /* customized debug */ + +/* look up password */ + +if (!(clearpass = expand_string(ob->spa_serverpassword))) + if (f.expand_string_forcedfail) + { + DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while " + "expanding spa_serverpassword\n"); + return FAIL; + } + else + { + DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding " + "spa_serverpassword: %s\n", expand_string_message); + return DEFER; + } + +/* create local hash copy */ + +spa_smb_encrypt(clearpass, challenge.challengeData, lmRespData); +spa_smb_nt_encrypt(clearpass, challenge.challengeData, ntRespData); + +/* compare NT hash (LM may not be available) */ + +off = IVAL(&responseptr->ntResponse.offset,0); +if (off >= sizeof(SPAAuthResponse) - 24) + { + DEBUG(D_auth) + debug_printf("auth_spa_server(): bad ntRespData spec in response\n"); + return FAIL; + } +s = (US responseptr) + off; + +if (memcmp(ntRespData, s, 24) == 0) + return auth_check_serv_cond(ablock); /* success. we have a winner. */ + + /* Expand server_condition as an authorization check (PH) */ + +return FAIL; +} + + +/************************************************* +* Client entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_spa_client( + auth_instance *ablock, /* authenticator block */ + void * sx, /* connection */ + int timeout, /* command timeout */ + uschar *buffer, /* buffer for reading response */ + int buffsize) /* size of buffer */ +{ +auth_spa_options_block *ob = + (auth_spa_options_block *)(ablock->options_block); +SPAAuthRequest request; +SPAAuthChallenge challenge; +SPAAuthResponse response; +char msgbuf[2048]; +char *domain = NULL; +char *username, *password; + +/* Code added by PH to expand the options */ + +*buffer = 0; /* Default no message when cancelled */ + +if (!(username = CS expand_string(ob->spa_username))) + { + if (f.expand_string_forcedfail) return CANCELLED; + string_format(buffer, buffsize, "expansion of \"%s\" failed in %s " + "authenticator: %s", ob->spa_username, ablock->name, + expand_string_message); + return ERROR; + } + +if (!(password = CS expand_string(ob->spa_password))) + { + if (f.expand_string_forcedfail) return CANCELLED; + string_format(buffer, buffsize, "expansion of \"%s\" failed in %s " + "authenticator: %s", ob->spa_password, ablock->name, + expand_string_message); + return ERROR; + } + +if (ob->spa_domain) + if (!(domain = CS expand_string(ob->spa_domain))) + { + if (f.expand_string_forcedfail) return CANCELLED; + string_format(buffer, buffsize, "expansion of \"%s\" failed in %s " + "authenticator: %s", ob->spa_domain, ablock->name, + expand_string_message); + return ERROR; + } + +/* Original code */ + +if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0) + return FAIL_SEND; + +/* wait for the 3XX OK message */ +if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout)) + return FAIL; + +DSPA("\n\n%s authenticator: using domain %s\n\n", ablock->name, domain); + +spa_build_auth_request(&request, CS username, domain); +spa_bits_to_base64(US msgbuf, US &request, spa_request_length(&request)); + +DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name, msgbuf); + +/* send the encrypted password */ +if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0) + return FAIL_SEND; + +/* wait for the auth challenge */ +if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout)) + return FAIL; + +/* convert the challenge into the challenge struct */ +DSPA("\n\n%s authenticator: challenge (%s)\n\n", ablock->name, buffer + 4); +spa_base64_to_bits(CS (&challenge), sizeof(challenge), CCS (buffer + 4)); + +spa_build_auth_response(&challenge, &response, CS username, CS password); +spa_bits_to_base64(US msgbuf, US &response, spa_request_length(&response)); +DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf); + +/* send the challenge response */ +if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0) + return FAIL_SEND; + +/* If we receive a success response from the server, authentication +has succeeded. There may be more data to send, but is there any point +in provoking an error here? */ + +if (smtp_read_response(sx, US buffer, buffsize, '2', timeout)) + return OK; + +/* Not a success response. If errno != 0 there is some kind of transmission +error. Otherwise, check the response code in the buffer. If it starts with +'3', more data is expected. */ + +if (errno != 0 || buffer[0] != '3') + return FAIL; + +return FAIL; +} + +#endif /*!MACRO_PREDEF*/ +/* End of spa.c */ diff --git a/src/auths/spa.h b/src/auths/spa.h new file mode 100644 index 0000000..ca93469 --- /dev/null +++ b/src/auths/spa.h @@ -0,0 +1,38 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* This file, which provides support for Microsoft's Secure Password +Authentication, was contributed by Marc Prud'hommeaux. */ + + +#include "auth-spa.h" + +/* Private structure for the private options. */ + +typedef struct { + uschar *spa_username; + uschar *spa_password; + uschar *spa_domain; + uschar *spa_serverpassword; +} auth_spa_options_block; + +/* Data for reading the private options. */ + +extern optionlist auth_spa_options[]; +extern int auth_spa_options_count; + +/* Block containing default values. */ + +extern auth_spa_options_block auth_spa_option_defaults; + +/* The entry points for the mechanism */ + +extern void auth_spa_init(auth_instance *); +extern int auth_spa_server(auth_instance *, uschar *); +extern int auth_spa_client(auth_instance *, void *, int, uschar *, int); + +/* End of spa.h */ diff --git a/src/auths/tls.c b/src/auths/tls.c new file mode 100644 index 0000000..325e7b4 --- /dev/null +++ b/src/auths/tls.c @@ -0,0 +1,94 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Jeremy Harris 1995 - 2020 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* This file provides an Exim authenticator driver for +a server to verify a client SSL certificate +*/ + + +#include "../exim.h" +#include "tls.h" + +/* Options specific to the tls authentication mechanism. */ + +optionlist auth_tls_options[] = { + { "server_param", opt_stringptr, + OPT_OFF(auth_tls_options_block, server_param1) }, + { "server_param1", opt_stringptr, + OPT_OFF(auth_tls_options_block, server_param1) }, + { "server_param2", opt_stringptr, + OPT_OFF(auth_tls_options_block, server_param2) }, + { "server_param3", opt_stringptr, + OPT_OFF(auth_tls_options_block, server_param3) }, +}; + +/* Size of the options list. An extern variable has to be used so that its +address can appear in the tables drtables.c. */ + +int auth_tls_options_count = nelem(auth_tls_options); + +/* Default private options block for the authentication method. */ + +auth_tls_options_block auth_tls_option_defaults = { + NULL, /* server_param1 */ + NULL, /* server_param2 */ + NULL, /* server_param3 */ +}; + + +#ifdef MACRO_PREDEF + +/* Dummy values */ +void auth_tls_init(auth_instance *ablock) {} +int auth_tls_server(auth_instance *ablock, uschar *data) {return 0;} +int auth_tls_client(auth_instance *ablock, void * sx, + int timeout, uschar *buffer, int buffsize) {return 0;} + +#else /*!MACRO_PREDEF*/ + + + + +/************************************************* +* Initialization entry point * +*************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + +void +auth_tls_init(auth_instance *ablock) +{ +ablock->public_name = ablock->name; /* needed for core code */ +} + + + +/************************************************* +* Server entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_tls_server(auth_instance *ablock, uschar *data) +{ +auth_tls_options_block * ob = (auth_tls_options_block *)ablock->options_block; + +if (ob->server_param1) + auth_vars[expand_nmax++] = expand_string(ob->server_param1); +if (ob->server_param2) + auth_vars[expand_nmax++] = expand_string(ob->server_param2); +if (ob->server_param3) + auth_vars[expand_nmax++] = expand_string(ob->server_param3); +return auth_check_serv_cond(ablock); +} + + +#endif /*!MACRO_PREDEF*/ +/* End of tls.c */ diff --git a/src/auths/tls.h b/src/auths/tls.h new file mode 100644 index 0000000..7aa95b6 --- /dev/null +++ b/src/auths/tls.h @@ -0,0 +1,30 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Jeremy Harris 2015 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Private structure for the private options. */ + +typedef struct { + uschar * server_param1; + uschar * server_param2; + uschar * server_param3; +} auth_tls_options_block; + +/* Data for reading the private options. */ + +extern optionlist auth_tls_options[]; +extern int auth_tls_options_count; + +/* Block containing default values. */ + +extern auth_tls_options_block auth_tls_option_defaults; + +/* The entry points for the mechanism */ + +extern void auth_tls_init(auth_instance *); +extern int auth_tls_server(auth_instance *, uschar *); + +/* End of tls.h */ diff --git a/src/auths/xtextdecode.c b/src/auths/xtextdecode.c new file mode 100644 index 0000000..746dfbd --- /dev/null +++ b/src/auths/xtextdecode.c @@ -0,0 +1,58 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2022 */ +/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* See the file NOTICE for conditions of use and distribution. */ + +#include "../exim.h" + + +/************************************************* +* Decode byte-string in xtext * +*************************************************/ + +/* This function decodes a string in xtextformat as defined in RFC 1891 and +required by the SMTP AUTH extension (RFC 2554). We put the result in a piece of +store of equal length - it cannot be longer than this. Although in general the +result of decoding an xtext may be binary, in the context in which it is used +by Exim (for decoding the value of AUTH on a MAIL command), the result is +expected to be an addr-spec. We therefore add on a terminating zero, for +convenience. + +Arguments: + code points to the coded string, zero-terminated + ptr where to put the pointer to the result, which is in + dynamic store + +Returns: the number of bytes in the result, excluding the final zero; + -1 if the input is malformed +*/ + +int +auth_xtextdecode(uschar *code, uschar **ptr) +{ +register int x; +uschar * result = store_get(Ustrlen(code) + 1, code); +*ptr = result; + +while ((x = (*code++)) != 0) + { + if (x < 33 || x > 127 || x == '=') return -1; + if (x == '+') + { + register int y; + if (!isxdigit((x = (*code++)))) return -1; + y = ((isdigit(x))? x - '0' : (tolower(x) - 'a' + 10)) << 4; + if (!isxdigit((x = (*code++)))) return -1; + *result++ = y | ((isdigit(x))? x - '0' : (tolower(x) - 'a' + 10)); + } + else *result++ = x; + } + +*result = 0; +return result - *ptr; +} + +/* End of xtextdecode.c */ diff --git a/src/auths/xtextencode.c b/src/auths/xtextencode.c new file mode 100644 index 0000000..fc571c7 --- /dev/null +++ b/src/auths/xtextencode.c @@ -0,0 +1,58 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2022 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* See the file NOTICE for conditions of use and distribution. */ + +#include "../exim.h" + + +/************************************************* +* Encode byte-string in xtext * +*************************************************/ + +/* This function encodes a string of bytes, containing any values whatsoever, +as "xtext", as defined in RFC 1891 and required by the SMTP AUTH extension (RFC +2554). + +Arguments: + clear points to the clear text bytes + len the number of bytes to encode + +Returns: a pointer to the zero-terminated xtext string, which + is in working store +*/ + +uschar * +auth_xtextencode(uschar *clear, int len) +{ +uschar *code; +uschar *p = US clear; +uschar *pp; +int c = len; +int count = 1; +register int x; + +/* We have to do a prepass to find out how many specials there are, +in order to get the right amount of store. */ + +while (c -- > 0) + count += ((x = *p++) < 33 || x > 127 || x == '+' || x == '=')? 3 : 1; + +pp = code = store_get(count, clear); + +p = US clear; +c = len; +while (c-- > 0) + if ((x = *p++) < 33 || x > 127 || x == '+' || x == '=') + pp += sprintf(CS pp, "+%.02x", x); /* There's always room */ + else + *pp++ = x; + +*pp = 0; +return code; +} + +/* End of xtextencode.c */ |