/* $Cambridge: exim/src/src/auths/spa.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * *************************************************/ /* Copyright (c) University of Cambridge 1995 - 2004 */ /* 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 * 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". */ #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 contidion 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) */ }; /************************************************* * 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) (((unsigned char *)(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 */ if (auth_get_no64_data(&data, US"NTLM supported") != OK) { /* something borked */ return FAIL; } if (spa_base64_to_bits((char *)(&request), (const char *)(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((char *)(&response), (const char *)(data)) < 0) { DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in " "response: %s\n", data); return FAIL; } /* get username and put it in $1 */ /*************************************************************** 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; } /***************************************************************/ expand_nstring[1] = msgbuf; expand_nlength[1] = Ustrlen(msgbuf); expand_nmax = 1; /* look up password */ clearpass = expand_string(ob->spa_serverpassword); if (clearpass == NULL) { if (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 OK; return FAIL; } /************************************************* * Client entry point * *************************************************/ /* For interface, see auths/README */ int auth_spa_client( auth_instance *ablock, /* authenticator block */ smtp_inblock *inblock, /* connection inblock */ smtp_outblock *outblock, /* connection outblock */ 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; if (smtp_write_command(outblock, FALSE, "AUTH %s\r\n", ablock->public_name) < 0) return FAIL_SEND; /* wait for the 3XX OK message */ if (!smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout)) return FAIL; /* Code added by PH to expand the options */ username = CS expand_string(ob->spa_username); if (username == NULL) { string_format(buffer, buffsize, "expansion of \"%s\" failed in %s " "authenticator: %s", ob->spa_username, ablock->name, expand_string_message); return ERROR; } password = CS expand_string(ob->spa_password); if (password == NULL) { 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 != NULL) { domain = CS expand_string(ob->spa_domain); if (domain == NULL) { string_format(buffer, buffsize, "expansion of \"%s\" failed in %s " "authenticator: %s", ob->spa_domain, ablock->name, expand_string_message); return ERROR; } } /* Original code */ 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(outblock, FALSE, "%s\r\n", msgbuf) < 0) return FAIL_SEND; /* wait for the auth challenge */ if (!smtp_read_response(inblock, (uschar *)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 ((char *)(&challenge), (const char *)(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(outblock, FALSE, "%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(inblock, 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; } /* End of spa.c */