diff options
Diffstat (limited to 'src/auths')
31 files changed, 7194 insertions, 0 deletions
diff --git a/src/auths/Makefile b/src/auths/Makefile new file mode 100644 index 0000000..62ce9d0 --- /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 \ + get_data.o get_no64_data.o gsasl_exim.o heimdal_gssapi.o \ + md5.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 +md5.o: $(HDRS) md5.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 +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..0e0c097 --- /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 SSL 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. + +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..d2c95c3 --- /dev/null +++ b/src/auths/auth-spa.c @@ -0,0 +1,1534 @@ +/************************************************* +* 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). + + * 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) \ +{ int l; for (l = 0; l < (len); l++) (val)[l] = macro((buf), (pos) + (size)*l); } + +# define SSMBMACRO(macro,buf,pos,val,len,size) \ +{ int l; for (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 \ + { int idx; for (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 \ + { int idx; for (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 \ + { int idx; for (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; + register 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) +{ + int i; + for (i = 0; i < n; i++) + out[i] = in[p[i] - 1]; +} + +static void +lshift (char *d, int count, int n) +{ + char out[64]; + int i; + for (i = 0; i < n; i++) + out[i] = d[(i + count) % n]; + for (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) +{ + int i; + for (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 != NULL && 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); + + request = request; /* Added by PH to stop compilers whinging */ + + /* 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..f96348c --- /dev/null +++ b/src/auths/call_pam.c @@ -0,0 +1,205 @@ +/************************************************* +* 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" + +/* 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 i; +int sep = 0; +struct pam_response *reply; + +if (pam_arg_ended) return PAM_CONV_ERR; + +reply = malloc(sizeof(struct pam_response) * num_msg); + +if (reply == NULL) return PAM_CONV_ERR; + +for (i = 0; i < num_msg; i++) + { + uschar *arg; + switch (msg[i]->msg_style) + { + case PAM_PROMPT_ECHO_ON: + case PAM_PROMPT_ECHO_OFF: + arg = string_nextinlist(&pam_args, &sep, big_buffer, big_buffer_size); + if (arg == NULL) + { + arg = US""; + pam_arg_ended = TRUE; + } + reply[i].resp = CS string_copy_malloc(arg); /* PAM frees resp */ + 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, big_buffer, big_buffer_size); +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..089f501 --- /dev/null +++ b/src/auths/call_pwcheck.c @@ -0,0 +1,120 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2015 */ +/* 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, (const char **)(&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..c363743 --- /dev/null +++ b/src/auths/call_radius.c @@ -0,0 +1,237 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* 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. */ + +/* ugly hack to work around redefinition of ENV by radiusclient.h and + * db.h: define _DB_H_ so the db.h include thinks it's already included, + * we can get away with it like this, since this file doesn't use any db + * functions. */ +#ifndef _DB_H_ +# define _DB_H_ 1 +# define _DB_EXT_PROT_IN_ 1 +# define DB void +#endif + +#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 + # 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 + + +user = string_nextinlist(&radius_args, &sep, big_buffer, big_buffer_size); +if (user == NULL) 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 = string_sprintf("RADIUS: can't read dictionary"); + +else if (rc_avpair_add(&send, PW_USER_NAME, user, 0) == NULL) + *errptr = string_sprintf("RADIUS: add user name failed\n"); + +else if (rc_avpair_add(&send, PW_USER_PASSWORD, CS radius_args, 0) == NULL) + *errptr = string_sprintf("RADIUS: add password failed\n"); + +else if (rc_avpair_add(&send, PW_SERVICE_TYPE, &service, 0) == NULL) + *errptr = string_sprintf("RADIUS: add service type failed\n"); + +#else /* RADIUS_LIB_RADIUSCLIENT unset => RADIUS_LIB_RADIUSCLIENT2 */ + +if ((h = rc_read_config(RADIUS_CONFIG_FILE)) == NULL) + *errptr = string_sprintf("RADIUS: can't open %s", RADIUS_CONFIG_FILE); + +else if (rc_read_dictionary(h, rc_conf_str(h, "dictionary")) != 0) + *errptr = string_sprintf("RADIUS: can't read dictionary"); + +else if (rc_avpair_add(h, &send, PW_USER_NAME, user, Ustrlen(user), 0) == NULL) + *errptr = string_sprintf("RADIUS: add user name failed\n"); + +else if (rc_avpair_add(h, &send, PW_USER_PASSWORD, CS radius_args, + Ustrlen(radius_args), 0) == NULL) + *errptr = string_sprintf("RADIUS: add password failed\n"); + +else if (rc_avpair_add(h, &send, PW_SERVICE_TYPE, &service, 0, 0) == NULL) + *errptr = string_sprintf("RADIUS: add service type failed\n"); + +#endif /* RADIUS_LIB_RADIUSCLIENT */ + +if (*errptr != NULL) + { + 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; + + default: + case BADRESP_RC: + *errptr = string_sprintf("RADIUS: unexpected response (%d)", result); + return ERROR; + } + +#else /* RADIUS_LIB_RADLIB is set */ + +/* Authenticate using the libradius library */ + +h = rad_auth_open(); +if (h == NULL) + { + *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 + { + result = rad_send_request(h); + + switch(result) + { + 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 != NULL) 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..dc299f1 --- /dev/null +++ b/src/auths/check_serv_cond.c @@ -0,0 +1,130 @@ +/************************************************* +* 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) + { + int i; + debug_printf("%s authenticator %s:\n", ablock->name, label); + for (i = 0; i < AUTH_VARS; i++) + { + if (auth_vars[i] != NULL) + debug_printf(" $auth%d = %s\n", i + 1, auth_vars[i]); + } + for (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 == NULL) return unset; +cond = expand_string(condition); + +HDEBUG(D_auth) + { + if (cond == NULL) + 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 == NULL) + { + 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..8e4794c --- /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 */ +/* 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, + (void *)(offsetof(auth_cram_md5_options_block, client_name)) }, + { "client_secret", opt_stringptr, + (void *)(offsetof(auth_cram_md5_options_block, client_secret)) }, + { "server_secret", opt_stringptr, + (void *)(offsetof(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 i; +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 (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 != 0) 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 != 0 && !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," 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(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..546c20b --- /dev/null +++ b/src/auths/cyrus_sasl.c @@ -0,0 +1,526 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* 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, + (void *)(offsetof(auth_cyrus_sasl_options_block, server_hostname)) }, + { "server_mech", opt_stringptr, + (void *)(offsetof(auth_cyrus_sasl_options_block, server_mech)) }, + { "server_realm", opt_stringptr, + (void *)(offsetof(auth_cyrus_sasl_options_block, server_realm)) }, + { "server_service", opt_stringptr, + (void *)(offsetof(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;} +void auth_cyrus_sasl_version_report(FILE *f) {} + +#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 != NULL) *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; +uschar *rs_point, *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 == NULL) + ob->server_mech = string_copy(ablock->public_name); + +expanded_hostname = expand_string(ob->server_hostname); +if (expanded_hostname == NULL) + 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 != NULL) { + realm_expanded = CS expand_string(ob->server_realm); + if (realm_expanded == NULL) + 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, "", ":", "", (const char **)&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_get(0); + +/* 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, i, 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); + } +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 (i = 0; i < 2; ++i) + { + struct sockaddr_storage ss; + int (*query)(int, struct sockaddr *, socklen_t *); + int propnum, port; + const uschar *label; + uschar *address, *address_port; + const char *s_err; + socklen_t sslen; + + if (i) + { + query = &getpeername; + propnum = SASL_IPREMOTEPORT; + label = CUS"peer"; + } + else + { + query = &getsockname; + propnum = SASL_IPLOCALPORT; + label = CUS"local"; + } + + sslen = sizeof(ss); + if ((rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen)) < 0) + { + HDEBUG(D_auth) + debug_printf("Failed to get %s address information: %s\n", + label, strerror(errno)); + break; + } + + address = host_ntoa(-1, &ss, NULL, &port); + address_port = string_sprintf("%s;%d", address, port); + + if ((rc = sasl_setprop(conn, propnum, address_port)) != SASL_OK) + { + s_err = sasl_errdetail(conn); + HDEBUG(D_auth) + 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, + (const char **)(&output), &outlen); + } + else + { + /* make sure that we have a null-terminated string */ + out2 = string_copyn(output, outlen); + + if ((rc = auth_get_data(&input, out2, 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, (const char **)(&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):\n " + "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):\n " + "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):\n " + "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):\n " + "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 * +*************************************************/ + +void +auth_cyrus_sasl_version_report(FILE *f) +{ +const char *implementation, *version; +sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL); +fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n" + " Runtime: %s [%s]\n", + SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, + version, implementation); +} + +/************************************************* +* 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..da6f3cd --- /dev/null +++ b/src/auths/cyrus_sasl.h @@ -0,0 +1,35 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* 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 void auth_cyrus_sasl_version_report(FILE *f); + +/* End of cyrus_sasl.h */ diff --git a/src/auths/dovecot.c b/src/auths/dovecot.c new file mode 100644 index 0000000..b1dde06 --- /dev/null +++ b/src/auths/dovecot.c @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru> + * Copyright (c) 2006-2017 The Exim Maintainers + * + * 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, + (void *)(offsetof(auth_dovecot_options_block, server_socket)) + }, +}; + +/* 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 = + sizeof(auth_dovecot_options) / sizeof(optionlist); + +/* Default private options block for the authentication method. */ + +auth_dovecot_options_block auth_dovecot_option_defaults = { + NULL, /* server_socket */ +}; + + + + +#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 == NULL) + ablock->public_name = ablock->name; + if (ob->server_socket != NULL) + 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 + 1; + *str = '\0'; + } + n++; + } + str++; + } + + /* 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, int fd) +{ +int p = 0; +int count = 0; + +for (;;) + { + if (socket_buffer_left == 0) + { + socket_buffer_left = read(fd, sbuffer, sizeof(sbuffer)); + if (socket_buffer_left == 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; +struct sockaddr_un sa; +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, cont = 1, fd = -1, ret = DEFER; +BOOL found = FALSE, have_mech_line = FALSE; + +HDEBUG(D_auth) debug_printf("dovecot authentication\n"); + +if (!data) + { + ret = FAIL; + goto out; + } + +memset(&sa, 0, sizeof(sa)); +sa.sun_family = AF_UNIX; + +/* This was the original code here: it is nonsense because strncpy() +does not return an integer. I have converted this to use the function +that formats and checks length. PH */ + +/* +if (strncpy(sa.sun_path, ob->server_socket, sizeof(sa.sun_path)) < 0) { +} +*/ + +if (!string_format(US sa.sun_path, sizeof(sa.sun_path), "%s", + ob->server_socket)) + { + auth_defer_msg = US"authentication socket path too long"; + return DEFER; + } + +auth_defer_msg = US"authentication socket connection error"; + +if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + return DEFER; + +if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) + goto out; + +auth_defer_msg = US"authentication socket protocol error"; + +socket_buffer_left = 0; /* Global, used to read more than a line but return by line */ +while (cont) + { + if (dc_gets(buffer, sizeof(buffer), fd) == NULL) + OUT("authentication socket read error or premature eof"); + 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, sizeof(args) / sizeof(args[0])); + + /* HDEBUG(D_auth) debug_strcut(args, nargs, sizeof(args) / sizeof(args[0])); */ + + /* 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); + cont = 0; + } + } + +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 != NULL) + 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 != NULL + && 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 (write(fd, 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", auth_command); + +while (1) + { + uschar *temp; + uschar *auth_id_pre = NULL; + int i; + + if (dc_gets(buffer, sizeof(buffer), fd) == NULL) + { + 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, sizeof(args) / sizeof(args[0])); + + 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 (write(fd, temp, Ustrlen(temp)) < 0) + OUT("authentication socket write error"); + break; + + case 'F': + CHECK_COMMAND("FAIL", 1, -1); + + for (i=2; (i<nargs) && (auth_id_pre == NULL); 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 (i=2; (i<nargs) && (auth_id_pre == NULL); 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 == NULL) + OUT("authentication socket protocol error, username missing"); + + ret = OK; + /* fallthrough */ + + default: + goto out; + } + } + +out: +/* close the socket used by dovecot */ +if (fd >= 0) + close(fd); + +/* 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..ea3f04d --- /dev/null +++ b/src/auths/dovecot.h @@ -0,0 +1,28 @@ +/************************************************* +* 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_socket; +} 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/get_data.c b/src/auths/get_data.c new file mode 100644 index 0000000..7d974ab --- /dev/null +++ b/src/auths/get_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 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. + +Arguments: + aptr set to point to the response (which is in big_buffer) + challenge the challenge text (unencoded, may be binary) + challen the length of the challenge text + +Returns: OK on success + BAD64 if response too large for buffer + CANCELLED if response is "*" +*/ + +int +auth_get_data(uschar **aptr, 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; +} + +/* 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..da833d5 --- /dev/null +++ b/src/auths/gsasl_exim.c @@ -0,0 +1,633 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* 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" + +/* 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. */ +optionlist auth_gsasl_options[] = { + { "server_channelbinding", opt_bool, + (void *)(offsetof(auth_gsasl_options_block, server_channelbinding)) }, + { "server_hostname", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_hostname)) }, + { "server_mech", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_mech)) }, + { "server_password", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_password)) }, + { "server_realm", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_realm)) }, + { "server_scram_iter", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_scram_iter)) }, + { "server_scram_salt", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_scram_salt)) }, + { "server_service", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_service)) } +}; +/* GSASL_SCRAM_SALTED_PASSWORD documented only for client, so not implementing +hooks to avoid cleartext passwords in the Exim server. */ + +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 = { + US"smtp", /* server_service */ + US"$primary_hostname", /* server_hostname */ + NULL, /* server_realm */ + NULL, /* server_mech */ + NULL, /* server_password */ + NULL, /* server_scram_iter */ + NULL, /* server_scram_salt */ + FALSE /* server_channelbinding */ +}; + + +#ifdef MACRO_PREDEF + +/* 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, smtp_inblock * sx, + int timeout, uschar *buffer, int buffsize) {return 0;} +void auth_gsasl_version_report(FILE *f) {} + +#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) +{ + char *p; + int rc, supported; + 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 == NULL) + 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 == NULL) { + rc = gsasl_init(&gsasl_ctx); + if (rc != 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. */ + rc = gsasl_server_mechlist(gsasl_ctx, &p); + if (rc != 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)); + HDEBUG(D_auth) debug_printf("GNU SASL supports: %s\n", p); + + supported = gsasl_client_support_p(gsasl_ctx, CCS ob->server_mech); + if (!supported) + 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 == NULL) && + (streqic(ob->server_mech, US"EXTERNAL") || + streqic(ob->server_mech, US"ANONYMOUS") || + streqic(ob->server_mech, US"PLAIN") || + streqic(ob->server_mech, US"LOGIN"))) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "Need server_condition for %s mechanism", + 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 == NULL) && + streqic(ob->server_mech, US"DIGEST-MD5")) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "Need server_realm for %s mechanism", + ablock->name, ob->server_mech); + + /* 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. + + So don't activate without server_condition, this might be relaxed in the future. + */ + if (ablock->server_condition != NULL) ablock->server = TRUE; + ablock->client = FALSE; +} + + +/* 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); + + HDEBUG(D_auth) + debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n", + prop, callback_loop); + + if (cb_state == NULL) { + HDEBUG(D_auth) debug_printf(" not from our server/client processing.\n"); + return GSASL_NO_CALLBACK; + } + + 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; +} + + +/************************************************* +* 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); + + rc = gsasl_server_start(gsasl_ctx, CCS ob->server_mech, &sctx); + if (rc != 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 */ + + gsasl_session_hook_set(sctx, (void *)ablock); + cb_state.ablock = ablock; + cb_state.currently = CURRENTLY_SERVER; + gsasl_session_hook_set(sctx, (void *)&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"); +#ifdef SUPPORT_TLS + if (tls_channelbinding_b64) { + /* Some auth mechanisms can ensure that both sides are talking within 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* + */ + if (ob->server_channelbinding) { + HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n", + ablock->name); + gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, + CCS tls_channelbinding_b64); + } 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 { + rc = gsasl_step64(sctx, received, &to_send); + + switch (rc) { + 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; + } + + if ((rc == GSASL_NEEDS_MORE) || + (to_send && *to_send)) + exim_error = + auth_get_no64_data((uschar **)&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; + + 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; + + exim_rc = auth_check_some_cond(ablock, label, condition_string, FAIL); + + if (exim_rc == OK) { + return GSASL_OK; + } else if (exim_rc == DEFER) { + sasl_error_should_defer = TRUE; + return GSASL_AUTHENTICATION_ERROR; + } else if (exim_rc == FAIL) { + return GSASL_AUTHENTICATION_ERROR; + } + + 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; +} + +static int +server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock) +{ + char *tmps; + uschar *propval; + int cbrc = GSASL_NO_CALLBACK; + int i; + auth_gsasl_options_block *ob = + (auth_gsasl_options_block *)(ablock->options_block); + + HDEBUG(D_auth) + debug_printf("GNU SASL callback %d for %s/%s as server\n", + prop, ablock->name, ablock->public_name); + + for (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 */ + propval = US gsasl_property_fast(sctx, GSASL_AUTHID); + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + propval = US gsasl_property_fast(sctx, GSASL_AUTHZID); + auth_vars[1] = expand_nstring[2] = propval ? propval : US""; + propval = US gsasl_property_fast(sctx, GSASL_PASSWORD); + auth_vars[2] = expand_nstring[3] = propval ? propval : US""; + expand_nmax = 3; + for (i = 1; i <= 3; ++i) + expand_nlength[i] = Ustrlen(expand_nstring[i]); + + cbrc = condition_check(ablock, US"server_condition", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_VALIDATE_EXTERNAL: + if (ablock->server_condition == NULL) { + HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL.\n"); + cbrc = GSASL_AUTHENTICATION_ERROR; + break; + } + propval = US gsasl_property_fast(sctx, GSASL_AUTHZID); + /* We always set $auth1, even if only to empty string. */ + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + expand_nlength[1] = Ustrlen(expand_nstring[1]); + expand_nmax = 1; + + cbrc = condition_check(ablock, + US"server_condition (EXTERNAL)", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_VALIDATE_ANONYMOUS: + if (ablock->server_condition == NULL) { + HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS.\n"); + cbrc = GSASL_AUTHENTICATION_ERROR; + break; + } + propval = US gsasl_property_fast(sctx, GSASL_ANONYMOUS_TOKEN); + /* We always set $auth1, even if only to empty string. */ + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + expand_nlength[1] = Ustrlen(expand_nstring[1]); + expand_nmax = 1; + + 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. */ + propval = US gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME); + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + propval = US gsasl_property_fast(sctx, GSASL_AUTHZID); + auth_vars[1] = expand_nstring[2] = propval ? propval : US""; + expand_nmax = 2; + for (i = 1; i <= 2; ++i) + expand_nlength[i] = Ustrlen(expand_nstring[i]); + + /* 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_PASSWORD: + /* DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM + CRAM-MD5: GSASL_AUTHID + PLAIN: GSASL_AUTHID and GSASL_AUTHZID + LOGIN: GSASL_AUTHID + */ + if (ob->server_scram_iter) { + tmps = CS expand_string(ob->server_scram_iter); + gsasl_property_set(sctx, GSASL_SCRAM_ITER, tmps); + } + if (ob->server_scram_salt) { + tmps = CS expand_string(ob->server_scram_salt); + gsasl_property_set(sctx, GSASL_SCRAM_SALT, tmps); + } + /* 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. */ + propval = US gsasl_property_fast(sctx, GSASL_AUTHID); + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + propval = US gsasl_property_fast(sctx, GSASL_AUTHZID); + auth_vars[1] = expand_nstring[2] = propval ? propval : US""; + propval = US gsasl_property_fast(sctx, GSASL_REALM); + auth_vars[2] = expand_nstring[3] = propval ? propval : US""; + expand_nmax = 3; + for (i = 1; i <= 3; ++i) + expand_nlength[i] = Ustrlen(expand_nstring[i]); + + tmps = CS expand_string(ob->server_password); + if (tmps == NULL) { + sasl_error_should_defer = f.expand_string_forcedfail ? FALSE : TRUE; + 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; + } + 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. */ + 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; +} + + +/************************************************* +* Client entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_gsasl_client( + auth_instance *ablock, /* authenticator block */ + smtp_inblock * 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; +} + +static int +client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock) +{ + int cbrc = GSASL_NO_CALLBACK; + HDEBUG(D_auth) + debug_printf("GNU SASL callback %d for %s/%s as client\n", + prop, ablock->name, ablock->public_name); + + HDEBUG(D_auth) + debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n"); + + return cbrc; +} + +/************************************************* +* Diagnostic API * +*************************************************/ + +void +auth_gsasl_version_report(FILE *f) +{ + const char *runtime; + runtime = gsasl_check_version(NULL); + fprintf(f, "Library version: GNU SASL: Compile: %s\n" + " Runtime: %s\n", + GSASL_VERSION, runtime); +} + +#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..8842165 --- /dev/null +++ b/src/auths/gsasl_exim.h @@ -0,0 +1,42 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* 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_scram_iter; + uschar *server_scram_salt; + BOOL server_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 *, smtp_inblock *, + int, uschar *, int); +extern void auth_gsasl_version_report(FILE *f); + +/* 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..11a7d39 --- /dev/null +++ b/src/auths/heimdal_gssapi.c @@ -0,0 +1,608 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* 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, + (void *)(offsetof(auth_heimdal_gssapi_options_block, server_hostname)) }, + { "server_keytab", opt_stringptr, + (void *)(offsetof(auth_heimdal_gssapi_options_block, server_keytab)) }, + { "server_service", opt_stringptr, + (void *)(offsetof(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;} +void auth_heimdal_gssapi_version_report(FILE *f) {} + +#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(uschar *, 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; + } + + krc = krb5_init_context(&context); + if (krc != 0) { + 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); + krc = krb5_kt_resolve(context, k_keytab_typed_name, &keytab); + if (krc) { + 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"); + krc = krb5_kt_default(context, &keytab); + if (krc) { + 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 */ + krc = krb5_kt_start_seq_get(context, keytab, &cursor); + if (krc) + exim_heimdal_error_debug("krb5_kt_start_seq_get", context, krc); + else { + while ((krc = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0) { + 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); + } + krc = krb5_kt_end_seq_get(context, keytab, &cursor); + if (krc) + exim_heimdal_error_debug("krb5_kt_end_seq_get", context, krc); + } + } + + krc = krb5_kt_close(context, keytab); + if (krc) + HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_close", context, krc); + + krb5_free_context(context); + + /* RFC 4121 section 5.2, SHOULD support 64K input buffers */ + if (big_buffer_size < (64 * 1024)) { + uschar *newbuf; + big_buffer_size = 64 * 1024; + newbuf = store_malloc(big_buffer_size); + store_free(big_buffer); + big_buffer = newbuf; + } + + 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, i; + uschar *tmp1, *tmp2, *from_client; + auth_heimdal_gssapi_options_block *ob = + (auth_heimdal_gssapi_options_block *)(ablock->options_block); + BOOL handled_empty_ir; + uschar *store_reset_point; + uschar *keytab; + uschar sasl_config[4]; + uschar requested_qop; + + store_reset_point = store_get(0); + + 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 == '\0') { + if (handled_empty_ir) { + HDEBUG(D_auth) debug_printf("gssapi: repeated empty input, grr.\n"); + error_out = BAD64; + goto ERROR_OUT; + } else { + HDEBUG(D_auth) debug_printf("gssapi: missing initial response, nudging.\n"); + error_out = auth_get_data(&from_client, US"", 0); + if (error_out != 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) == 0) { + HDEBUG(D_auth) + debug_printf("gssapi: client requested security layers (%x)\n", + (unsigned int) requested_qop); + error_out = FAIL; + goto ERROR_OUT; + } + + for (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(uschar *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, TRUE, 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 * +*************************************************/ + +void +auth_heimdal_gssapi_version_report(FILE *f) +{ + /* No build-time constants available unless we link against libraries at + build-time and export the result as a string into a header ourselves. */ + fprintf(f, "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..a606a5c --- /dev/null +++ b/src/auths/heimdal_gssapi.h @@ -0,0 +1,39 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* 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 *, smtp_inblock *, + smtp_outblock *, int, uschar *, int); +extern void auth_heimdal_gssapi_version_report(FILE *f); + +/* End of heimdal_gssapi.h */ diff --git a/src/auths/md5.c b/src/auths/md5.c new file mode 100644 index 0000000..8accdb9 --- /dev/null +++ b/src/auths/md5.c @@ -0,0 +1,357 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* See the file NOTICE for conditions of use and distribution. */ + +#ifndef STAND_ALONE +#include "../exim.h" + +/* For stand-alone testing, we need to have the structure defined, and +to be able to do I/O */ + +#else +#include <stdio.h> +#include "../mytypes.h" +typedef struct md5 { + unsigned int length; + unsigned int abcd[4]; + } +md5; +#endif + + + +/************************************************* +* Start off a new MD5 computation. * +*************************************************/ + +/* +Argument: pointer to md5 storage structure +Returns: nothing +*/ + +void +md5_start(md5 *base) +{ +base->abcd[0] = 0x67452301; +base->abcd[1] = 0xefcdab89; +base->abcd[2] = 0x98badcfe; +base->abcd[3] = 0x10325476; +base->length = 0; +} + + + +/************************************************* +* Process another 64-byte block * +*************************************************/ + +/* This function implements central part of the algorithm which is described +in RFC 1321. + +Arguments: + base pointer to md5 storage structure + text pointer to next 64 bytes of subject text + +Returns: nothing +*/ + +void +md5_mid(md5 *base, const uschar *text) +{ +register unsigned int a = base->abcd[0]; +register unsigned int b = base->abcd[1]; +register unsigned int c = base->abcd[2]; +register unsigned int d = base->abcd[3]; +int i; +unsigned int X[16]; +base->length += 64; + +/* Load the 64 bytes into a set of working integers, treating them as 32-bit +numbers in little-endian order. */ + +for (i = 0; i < 16; i++) + { + X[i] = (unsigned int)(text[0]) | + ((unsigned int)(text[1]) << 8) | + ((unsigned int)(text[2]) << 16) | + ((unsigned int)(text[3]) << 24); + text += 4; + } + +/* For each round of processing there is a function to be applied. We define it +as a macro each time round. */ + +/*********************************************** +* Round 1 * +* F(X,Y,Z) = XY v not(X) Z * +* a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s) * +***********************************************/ + +#define OP(a, b, c, d, k, s, ti) \ + a += ((b & c) | (~b & d)) + X[k] + (unsigned int)ti; \ + a = b + ((a << s) | (a >> (32 - s))) + +OP(a, b, c, d, 0, 7, 0xd76aa478); +OP(d, a, b, c, 1, 12, 0xe8c7b756); +OP(c, d, a, b, 2, 17, 0x242070db); +OP(b, c, d, a, 3, 22, 0xc1bdceee); +OP(a, b, c, d, 4, 7, 0xf57c0faf); +OP(d, a, b, c, 5, 12, 0x4787c62a); +OP(c, d, a, b, 6, 17, 0xa8304613); +OP(b, c, d, a, 7, 22, 0xfd469501); +OP(a, b, c, d, 8, 7, 0x698098d8); +OP(d, a, b, c, 9, 12, 0x8b44f7af); +OP(c, d, a, b, 10, 17, 0xffff5bb1); +OP(b, c, d, a, 11, 22, 0x895cd7be); +OP(a, b, c, d, 12, 7, 0x6b901122); +OP(d, a, b, c, 13, 12, 0xfd987193); +OP(c, d, a, b, 14, 17, 0xa679438e); +OP(b, c, d, a, 15, 22, 0x49b40821); + +#undef OP + +/*********************************************** +* Round 2 * +* F(X,Y,Z) = XZ v Y not(Z) * +* a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s) * +***********************************************/ + +#define OP(a, b, c, d, k, s, ti) \ + a += ((b & d) | (c & ~d)) + X[k] + (unsigned int)ti; \ + a = b + ((a << s) | (a >> (32 - s))) + +OP(a, b, c, d, 1, 5, 0xf61e2562); +OP(d, a, b, c, 6, 9, 0xc040b340); +OP(c, d, a, b, 11, 14, 0x265e5a51); +OP(b, c, d, a, 0, 20, 0xe9b6c7aa); +OP(a, b, c, d, 5, 5, 0xd62f105d); +OP(d, a, b, c, 10, 9, 0x02441453); +OP(c, d, a, b, 15, 14, 0xd8a1e681); +OP(b, c, d, a, 4, 20, 0xe7d3fbc8); +OP(a, b, c, d, 9, 5, 0x21e1cde6); +OP(d, a, b, c, 14, 9, 0xc33707d6); +OP(c, d, a, b, 3, 14, 0xf4d50d87); +OP(b, c, d, a, 8, 20, 0x455a14ed); +OP(a, b, c, d, 13, 5, 0xa9e3e905); +OP(d, a, b, c, 2, 9, 0xfcefa3f8); +OP(c, d, a, b, 7, 14, 0x676f02d9); +OP(b, c, d, a, 12, 20, 0x8d2a4c8a); + +#undef OP + +/*********************************************** +* Round 3 * +* F(X,Y,Z) = X xor Y xor Z * +* a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s) * +***********************************************/ + +#define OP(a, b, c, d, k, s, ti) \ + a += (b ^ c ^ d) + X[k] + (unsigned int)ti; \ + a = b + ((a << s) | (a >> (32 - s))) + +OP(a, b, c, d, 5, 4, 0xfffa3942); +OP(d, a, b, c, 8, 11, 0x8771f681); +OP(c, d, a, b, 11, 16, 0x6d9d6122); +OP(b, c, d, a, 14, 23, 0xfde5380c); +OP(a, b, c, d, 1, 4, 0xa4beea44); +OP(d, a, b, c, 4, 11, 0x4bdecfa9); +OP(c, d, a, b, 7, 16, 0xf6bb4b60); +OP(b, c, d, a, 10, 23, 0xbebfbc70); +OP(a, b, c, d, 13, 4, 0x289b7ec6); +OP(d, a, b, c, 0, 11, 0xeaa127fa); +OP(c, d, a, b, 3, 16, 0xd4ef3085); +OP(b, c, d, a, 6, 23, 0x04881d05); +OP(a, b, c, d, 9, 4, 0xd9d4d039); +OP(d, a, b, c, 12, 11, 0xe6db99e5); +OP(c, d, a, b, 15, 16, 0x1fa27cf8); +OP(b, c, d, a, 2, 23, 0xc4ac5665); + +#undef OP + +/*********************************************** +* Round 4 * +* F(X,Y,Z) = Y xor (X v not(Z)) * +* a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s) * +***********************************************/ + +#define OP(a, b, c, d, k, s, ti) \ + a += (c ^ (b | ~d)) + X[k] + (unsigned int)ti; \ + a = b + ((a << s) | (a >> (32 - s))) + +OP(a, b, c, d, 0, 6, 0xf4292244); +OP(d, a, b, c, 7, 10, 0x432aff97); +OP(c, d, a, b, 14, 15, 0xab9423a7); +OP(b, c, d, a, 5, 21, 0xfc93a039); +OP(a, b, c, d, 12, 6, 0x655b59c3); +OP(d, a, b, c, 3, 10, 0x8f0ccc92); +OP(c, d, a, b, 10, 15, 0xffeff47d); +OP(b, c, d, a, 1, 21, 0x85845dd1); +OP(a, b, c, d, 8, 6, 0x6fa87e4f); +OP(d, a, b, c, 15, 10, 0xfe2ce6e0); +OP(c, d, a, b, 6, 15, 0xa3014314); +OP(b, c, d, a, 13, 21, 0x4e0811a1); +OP(a, b, c, d, 4, 6, 0xf7537e82); +OP(d, a, b, c, 11, 10, 0xbd3af235); +OP(c, d, a, b, 2, 15, 0x2ad7d2bb); +OP(b, c, d, a, 9, 21, 0xeb86d391); + +#undef OP + +/* Add the new values back into the accumulators. */ + +base->abcd[0] += a; +base->abcd[1] += b; +base->abcd[2] += c; +base->abcd[3] += d; +} + + + + +/************************************************* +* Process the final text string * +*************************************************/ + +/* The string may be of any length. It is padded out according to the rules +for computing MD5 digests. The final result is then converted to text form +and returned. + +Arguments: + base pointer to the md5 storage structure + text pointer to the final text vector + length length of the final text vector + digest points to 16 bytes in which to place the result + +Returns: nothing +*/ + +void +md5_end(md5 *base, const uschar *text, int length, uschar *digest) +{ +int i; +uschar work[64]; + +/* Process in chunks of 64 until we have less than 64 bytes left. */ + +while (length >= 64) + { + md5_mid(base, text); + text += 64; + length -= 64; + } + +/* If the remaining string contains more than 55 bytes, we must pad it +out to 64, process it, and then set up the final chunk as 56 bytes of +padding. If it has less than 56 bytes, we pad it out to 56 bytes as the +final chunk. */ + +memcpy(work, text, length); +work[length] = 0x80; + +if (length > 55) + { + memset(work+length+1, 0, 63-length); + md5_mid(base, work); + base->length -= 64; + memset(work, 0, 56); + } +else + { + memset(work+length+1, 0, 55-length); + } + +/* The final 8 bytes of the final chunk are a 64-bit representation of the +length of the input string *bits*, before padding, low order word first, and +low order bytes first in each word. This implementation is designed for short +strings, and so operates with a single int counter only. */ + +length += base->length; /* Total length in bytes */ +length <<= 3; /* Total length in bits */ + +work[56] = length & 0xff; +work[57] = (length >> 8) & 0xff; +work[58] = (length >> 16) & 0xff; +work[59] = (length >> 24) & 0xff; + +memset(work+60, 0, 4); + +/* Process the final 64-byte chunk */ + +md5_mid(base, work); + +/* Pass back the result, low-order byte first in each word. */ + +for (i = 0; i < 4; i++) + { + register int x = base->abcd[i]; + *digest++ = x & 0xff; + *digest++ = (x >> 8) & 0xff; + *digest++ = (x >> 16) & 0xff; + *digest++ = (x >> 24) & 0xff; + } +} + + + +/************************************************* +************************************************** +* Stand-alone test program * +************************************************** +*************************************************/ + +#if defined STAND_ALONE & !defined CRAM_STAND_ALONE + +/* Test values */ + +static uschar *tests[] = { + "", "d41d8cd98f00b204e9800998ecf8427e", + + "a", "0cc175b9c0f1b6a831c399e269772661", + + "abc", "900150983cd24fb0d6963f7d28e17f72", + + "message digest", "f96b697d7cb7938d525a2f31aaf161d0", + + "abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b", + + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "d174ab98d277d9f5a5611c2c9f419d9f", + + "1234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890", + "57edf4a22be3c955ac49da2e2107b67a", + + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "a0842fcc02167127b0bb9a7c38e71ba8" +}; + +int main(void) +{ +md5 base; +int i = 0x01020304; +uschar *ctest = US (&i); +uschar buffer[256]; +uschar digest[16]; +printf("Checking md5: %s-endian\n", (ctest[0] == 0x04)? "little" : "big"); + +for (i = 0; i < sizeof(tests)/sizeof(uschar *); i += 2) + { + int j; + uschar s[33]; + printf("%s\nShould be: %s\n", tests[i], tests[i+1]); + md5_start(&base); + md5_end(&base, tests[i], strlen(tests[i]), digest); + for (j = 0; j < 16; j++) sprintf(s+2*j, "%02x", digest[j]); + printf("Computed: %s\n", s); + if (strcmp(s, tests[i+1]) != 0) printf("*** No match ***\n"); + printf("\n"); + } +} +#endif + +/* End of md5.c */ diff --git a/src/auths/plaintext.c b/src/auths/plaintext.c new file mode 100644 index 0000000..7a0f788 --- /dev/null +++ b/src/auths/plaintext.c @@ -0,0 +1,307 @@ +/************************************************* +* 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" +#include "plaintext.h" + + +/* Options specific to the plaintext authentication mechanism. */ + +optionlist auth_plaintext_options[] = { + { "client_ignore_invalid_base64", opt_bool, + (void *)(offsetof(auth_plaintext_options_block, client_ignore_invalid_base64)) }, + { "client_send", opt_stringptr, + (void *)(offsetof(auth_plaintext_options_block, client_send)) }, + { "server_prompts", opt_stringptr, + (void *)(offsetof(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 *clear, *end, *s; +int number = 1; +int len, rc; +int sep = 0; + +/* Expand a non-empty list of prompt strings */ + +if (prompts != NULL) + { + prompts = expand_cstring(prompts); + if (prompts == NULL) + { + 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 != 0) + { + if (Ustrcmp(data, "=") == 0) + { + auth_vars[0] = expand_nstring[++expand_nmax] = US""; + expand_nlength[expand_nmax] = 0; + } + else + { + if ((len = b64decode(data, &clear)) < 0) return BAD64; + end = clear + len; + while (clear < end && expand_nmax < EXPAND_MAXN) + { + 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]; + } + } + } + +/* 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, big_buffer, big_buffer_size)) + != NULL && expand_nmax < EXPAND_MAXN) + { + if (number++ <= expand_nmax) continue; + if ((rc = auth_get_data(&data, s, Ustrlen(s))) != OK) return rc; + if ((len = b64decode(data, &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); + } + +/* 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; +uschar *s; +BOOL first = TRUE; +int sep = 0; +int auth_var_idx = 0; + +/* 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, big_buffer, big_buffer_size))) + { + int i, len, clear_len; + uschar *ss = expand_string(s); + uschar *clear; + + /* 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) + { + uschar *ssave = string_copy(s); + if (!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", ssave, 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. */ + + for (i = 0; i < len; i++) + if (ss[i] == '^') + if (ss[i+1] != '^') + ss[i] = 0; + else + { + i++; + len--; + memmove(ss + i, ss + i + 1, len - i); + } + + /* The first string is attached to the AUTH command; others are sent + unembellished. */ + + if (first) + { + first = FALSE; + if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s%s%s\r\n", + ablock->public_name, len == 0 ? "" : " ", b64encode(ss, len)) < 0) + return FAIL_SEND; + } + else + { + if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(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, 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; + + /* If there is no more data to send, we have to cancel the authentication + exchange and return ERROR. */ + + if (!text) + { + 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 (!ob->client_ignore_invalid_base64) + { + 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; + } + clear = US""; + clear_len = 0; + } + + if (auth_var_idx < AUTH_VARS) + auth_vars[auth_var_idx++] = string_copy(clear); + } + +/* 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..54ba80f --- /dev/null +++ b/src/auths/pwcheck.c @@ -0,0 +1,453 @@ +/* SASL server API implementation + * Rob Siemborski + * Tim Martin + * $Id: checkpw.c,v 1.49 2002/03/07 19:14:04 ken3 Exp $ + */ +/* + * 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) +{ +userid = userid; /* Keep picky compilers happy */ +passwd = passwd; +*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) +{ +userid = userid; /* Keep picky compilers happy */ +passwd = passwd; +service = service; +realm = realm; +*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 { + *retval = store_get(count + 1); + 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..97e3b10 --- /dev/null +++ b/src/auths/spa.c @@ -0,0 +1,373 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* 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, + (void *)(offsetof(auth_spa_options_block, spa_domain)) }, + { "client_password", opt_stringptr, + (void *)(offsetof(auth_spa_options_block, spa_password)) }, + { "client_username", opt_stringptr, + (void *)(offsetof(auth_spa_options_block, spa_username)) }, + { "server_password", opt_stringptr, + (void *)(offsetof(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; + +/* send a 334, MS Exchange style, and grab the client's request, +unless we already have it via an initial response. */ + +if ((*data == '\0') && + (auth_get_no64_data(&data, US"NTLM supported") != OK)) + { + /* something borked */ + 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, (unsigned char*)&challenge, + spa_request_length(&challenge)); + +if (auth_get_no64_data(&data, msgbuf) != OK) + { + /* something borked */ + 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 = ((char*)responseptr) + IVAL(&responseptr->uUser.offset,0); + int len = SVAL(&responseptr->uUser.len,0)/2; + + 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 */ + +clearpass = expand_string(ob->spa_serverpassword); +if (clearpass == NULL) + { + 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) */ + +if (memcmp(ntRespData, + ((unsigned char*)responseptr)+IVAL(&responseptr->ntResponse.offset,0), + 24) == 0) + /* success. we have a winner. */ + { + return auth_check_serv_cond(ablock); + } + + /* 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, (unsigned char*)&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, (unsigned char*)&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..56f5f5e --- /dev/null +++ b/src/auths/tls.c @@ -0,0 +1,94 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Jeremy Harris 1995 - 2018 */ +/* 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, + (void *)(offsetof(auth_tls_options_block, server_param1)) }, + { "server_param1", opt_stringptr, + (void *)(offsetof(auth_tls_options_block, server_param1)) }, + { "server_param2", opt_stringptr, + (void *)(offsetof(auth_tls_options_block, server_param2)) }, + { "server_param3", opt_stringptr, + (void *)(offsetof(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..bf2a2a1 --- /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 sa.h */ diff --git a/src/auths/xtextdecode.c b/src/auths/xtextdecode.c new file mode 100644 index 0000000..5312acf --- /dev/null +++ b/src/auths/xtextdecode.c @@ -0,0 +1,57 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* 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); +*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..2c00c4a --- /dev/null +++ b/src/auths/xtextencode.c @@ -0,0 +1,57 @@ +/************************************************* +* 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" + + +/************************************************* +* 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); + +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 */ |