#pragma module RAD_MX_AUTH_CALLOUT "RAD_MX_AUTH_CALLOUT-1-A" #define __MODULE__ "RAD_MX_AUTH_CALLOUT" /* * auth_callout_example.c * * Example of an authentication callout for use with the MX SMTP server. * * Copyright © 2000, MadGoat Software. All Rights Reserved. * * MODULE DESCRIPTION: * * This module contains routines that implement an alternative * authentication source for use with the AUTH PLAIN and AUTH LOGIN * authentication mechanisms implemented by the MX SMTP server. It * may also provide an optional accounting callout, which is called * by the SMTP server after each message accepted from the authenticated * client. * * Building the callout module (VAX): * * $ CC/DECC RAD_MX_AUTH_CALLOUT * $ LINK/NOTRACE/SHARE RAD_MX_AUTH_CALLOUT.OBJ, SYS$INPUT:/OPTION * UNIVERSAL=INIT,AUTHENTICATE,ACCOUNTING,CLEANUP * ^Z ! (ctrl/Z) * * Building the callout module (Alpha): * * $ CC RAD_MX_AUTH_CALLOUT * $ LINK/NOTRACE/SHARE RAD_MX_AUTH_CALLOUT.OBJ, SYS$INPUT:/OPTION * SYMBOL_VECTOR=(INIT=PROCEDURE,AUTHENTICATE=PROCEDURE,- * ACCOUNTING=PROCEDURE,CLEANUP=PROCEDURE) * ^Z ! (ctrl/Z) * * Installing the callout: * $ DEFINE/SYSTEM/EXEC MX_SITE_SMTP_AUTHENTICATION dev:[dir]RAD_MX_AUTH_CALLOUT.EXE * $ MCP SET SMTP/AUTHENTICATION=PLAIN * $ MCP SHUTDOWN SMTP_SERVER * $ @SYS$STARTUP:MX_STARTUP SMTP_SERVER */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "radius.h" #define __NEW_STARLET 1 #include int debug_flag = 0; #define DEBUG if(debug_flag)printf("%-24.24s:%-08.0u ",__MODULE__,__LINE__);if(debug_flag)printf const char accmode = PSL$C_EXEC; $DESCRIPTOR(dsc_ucxdev, "UCX$DEVICE"); $DESCRIPTOR(dsc_tcpipdev, "TCPIP$DEVICE"); $DESCRIPTOR(dsc_radauth_addr, "RADIUS$AUTH_SERVER"); $DESCRIPTOR(dsc_radauth_port, "RADIUS$AUTH_PORT"); $DESCRIPTOR(dsc_secret, "RADIUS$SECRET"); $DESCRIPTOR(dsc_radtmo, "RADIUS$TIMEOUT"); $DESCRIPTOR(dsc_radretry, "RADIUS$RETRY"); $DESCRIPTOR(dsc_table, "LNM$SYSTEM_TABLE"); char obuf [256],ibuf[1024],secret[64]; unsigned short buflen,secretlen,port; unsigned short chan = 0,hostlen = 0; int ipaddr = 0,delta_tmo[2],retry,i,randno,len,tmo,reqidt = 0; struct sockaddr_in sock_host = {INET$C_AF_INET,0,INET$C_INADDR_ANY,0}; ILE2 loc_host = {sizeof(struct sockaddr_in),0,&sock_host}; ILE3 rem_host = {sizeof(struct sockaddr_in),0,&sock_host,&hostlen}; ILE3 itmlst[] = {{sizeof(obuf),LNM$_STRING,obuf,&buflen},{0,0,0,0}}; /* * ROUTINE: INIT * * DESCRIPTION: * Initializes an authentication session. Responsible for allocating and initializing * a context block for later authentication and accounting. This routine is called once * per SMTP session for which authentication is requested. * * PARAMETERS: * ctxptr: context block address, passed by reference * * RETURNS: VMS condition value * */ unsigned int INIT (void **ctxptr) { unsigned status = SS$_NORMAL; struct { short proto; char type; char domain; } sck_parm = {INET$C_UDP,INET_PROTYP$C_DGRAM,INET$C_AF_INET}; iosb netiosb; DEBUG("chan = %u\n",chan); if ( !chan ) { /* ** Get parameters from logicals */ if ( !(1 & (status = sys$trnlnm (0,&dsc_table,&dsc_radauth_addr,&accmode,&itmlst))) ) return status; obuf[buflen] = '\0'; if ( -1 == (ipaddr = inet_addr(obuf)) ) return SS$_INSFARG; DEBUG("%.*s = %.*s\n",dsc_radauth_addr.dsc$w_length,dsc_radauth_addr.dsc$a_pointer, buflen,obuf); if ( !(1 & (status = sys$trnlnm (0,&dsc_table,&dsc_radauth_port,&accmode,&itmlst))) ) port = PW_AUTH_UDP_PORT; else if ( !lib$cvt_dtb(buflen,obuf,&port) ) return SS$_INSFARG; DEBUG("%.*s = %u\n",dsc_radauth_port.dsc$w_length,dsc_radauth_port.dsc$a_pointer, port); if ( !(1 & (status = sys$trnlnm (0,&dsc_table,&dsc_radretry,&accmode,&itmlst))) ) retry = 5; else if ( !lib$cvt_dtb(buflen,obuf,&retry) ) return SS$_INSFARG; DEBUG("%.*s = %u\n",dsc_radretry.dsc$w_length,dsc_radretry.dsc$a_pointer,retry); /* ** If timeout value is not specified use 5 seconds */ if ( !(1 & (status = sys$trnlnm (0,&dsc_table,&dsc_radtmo,&accmode,&itmlst))) ) tmo = 5; else if ( !lib$cvt_dtb(buflen,obuf,&tmo) ) return SS$_INSFARG; i = LIB$K_DELTA_SECONDS; if ( !(1 & (status = lib$cvt_to_internal_time(&i,&tmo,delta_tmo))) ) return status; DEBUG("%.*s = %u\n",dsc_radtmo.dsc$w_length,dsc_radtmo.dsc$a_pointer,tmo); if ( !(1 & (status = sys$trnlnm (0,&dsc_table,&dsc_secret,&accmode,&itmlst))) ) return status; memcpy(secret,obuf,buflen); secretlen = buflen; DEBUG("%.*s = %.*s\n",dsc_secret.dsc$w_length,dsc_secret.dsc$a_pointer, buflen,obuf); /* ** Initialize network stuff */ if ( !(1 & (status = sys$assign( &dsc_ucxdev,&chan,0,0))) ) { if ( status != SS$_NOSUCHDEV ) return status; if ( !(1 & (status = sys$assign( &dsc_tcpipdev,&chan,0,0))) ) return status; } /* ** Create a UDP device */ sock_host.sin_family = INET$C_AF_INET; status = sys$qiow (0,chan,IO$_SETMODE,&netiosb, 0,0,&sck_parm,0,&loc_host,0,0,0); if ( !(status & 1) || !(netiosb.iosb$w_status & 1) ) { sys$dassgn (chan); return (1 & status)?netiosb.iosb$w_status:status; } sock_host.sin_addr.s_addr = ipaddr; sock_host.sin_port = htons(port); DEBUG("chan = %u\n",chan); } return status; } /* INIT */ /* **++ ** FUNCTIONAL DESCRIPTION: ** ** Canceling all queued and processed request due of expiration timer. ** ** FORMAL PARAMETERS: ** ** reqidt: pointer to a network channel ** ** RETURN VALUE: ** ** None ** ** SIDE EFFECTS: ** ** Cancel _all_ request for the given I/O channel. **-- */ void timer_ast ( int *reqidt ) { sys$cantim(reqidt,0); sys$cancel(*reqidt); } /* * ROUTINE: AUTHENTICATE * * DESCRIPTION: * Performs authentication for a username/password combination. If this routine requires * any I/O operation that may not complete immediately, it should use asynchronous I/O * and its AST completion routine should call the AST routine that is passed in by the * caller. * * Only one authentication request will ever be outstanding for a single authentication * context, so the context block can be used to store the caller's AST routine address, AST parameter, * and authentication status address for later use by its AST completion routine. * * Note that the SMTP server provides the username and password _exactly_ as sent by * the client. No case conversion, blank stripping, or other editing is done by the * server. * * PARAMETERS: * ctxptr: (in) context block address (as returned by INIT routine), passed by reference * usrnam: (in) username provided by client, passed by descriptor * pass: (in) password provided by client, passed by descriptor * cliaddr: (in) socket address of the client, passed by reference * cliaddrlen: (in) length of cliaddr socket address, passed by value * sessid: (out) authentication session ID, passed by reference * authstatus: (out) cond_value indicating success/failure of authentication, passed by reference * astadr: (in) address of caller's AST completion routine, passed by value * asptrm: (in) parameter to caller's AST routine, passed by value * * RETURNS: VMS condition value * - success status indicates that asynchronous I/O was started and caller should * expect its AST completion routine to be called * - non-success status indicates that the operation completed synchronously and * that the authstatus argument is valid immediately upon return from this routine */ unsigned int AUTHENTICATE (void **ctxptr, const struct dsc$descriptor *usrnam, const struct dsc$descriptor *pwd, const struct sockaddr *cliaddr, int cliaddrlen, unsigned int *sessid, unsigned int *authstatus, void *astadr, void *astprm) { int status; iosb netiosb; char vector[AUTH_VECTOR_LEN],hash[AUTH_VECTOR_LEN],*ptr, *user = usrnam->dsc$a_pointer, *pass = pwd->dsc$a_pointer; unsigned short userlen = usrnam->dsc$w_length,passlen = pwd->dsc$w_length; AUTH_HDR *auth_req = (AUTH_HDR *) obuf,*auth_ans = (AUTH_HDR *) ibuf; DEBUG("user = '%.*s'\n",userlen,user); DEBUG("pass = '%.*s'\n",passlen,pass); /* ** Prepare AUTH/REQUEST packet */ for (srand(time(0)),i = 0;i < AUTH_VECTOR_LEN;i += sizeof(int)) { randno = rand(); memcpy(&auth_req->vector[i], &randno, sizeof(int)); } md5_calc1(hash,secret,secretlen,auth_req->vector,AUTH_VECTOR_LEN); /* ** Fill header */ auth_req->code = PW_AUTHENTICATION_REQUEST; auth_req->id = (char) rand(); ptr = auth_req->data; len = AUTH_HDR_LEN; /* ** Construct a complex username from prefix,username and suffix */ *ptr++ = PW_USER_NAME; *ptr++ = userlen + 2; len += userlen + 2; memcpy(ptr,user,userlen); ptr += userlen; /* ** Hash the password with a shared secret, and put it into the packet */ *ptr++ = PW_PASSWORD; *ptr++ = AUTH_PASS_LEN + 2; len += AUTH_PASS_LEN + 2; for (i = 0;i < min(passlen,AUTH_PASS_LEN);i++) *ptr++ = hash[i] ^ pass[i]; for (;i < AUTH_PASS_LEN;i++) *ptr++ = hash[i] ^ 0; auth_req->length = htons(len); /* ** Send REQUEST & waiting ACK/REJECT */ reqidt = chan; for (i = 0;i < retry;i++,status = SS$_INVLOGIN) { /* ** Queue the read request */ if ( !(1 & (status = sys$qiow (0,chan,IO$_WRITEVBLK,&netiosb,0,0, &obuf,len,&rem_host,0,0,0))) ) break; /* ** Prepare to timeout processing */ if ( !(1 & (status = sys$setimr(0, &delta_tmo,timer_ast,&reqidt,0))) ) break; /* ** Wait for an answer to request from RADIUS server */ if ( !(1 & (status = sys$qiow (0,chan,IO$_READVBLK,&netiosb,0,0, &ibuf,sizeof(ibuf),&rem_host,0,0,0))) ) break; /* ** Check status, byte count, and remote IP address */ if ( netiosb.iosb$w_bcnt && (netiosb.iosb$w_status & 1) && (auth_ans->id == auth_req->id) && sock_host.sin_addr.s_addr == ipaddr ) break; } DEBUG("status = %u\n",status); DEBUG("netiosb.iosb$w_status = %u\n",netiosb.iosb$w_status); DEBUG("netiosb.iosb$w_bcnt = %u\n",netiosb.iosb$w_bcnt); sys$cantim(&reqidt,0); /* ** If have not got an answer from a remote RADIUS then performs checking against ** a local SYSUAF */ if ( !(1 & status) || !netiosb.iosb$w_bcnt || !(netiosb.iosb$w_status & 1) ) return SS$_INVLOGIN; DEBUG("auth_ans->code = %u\n",auth_ans->code); /* ** Processing ACK/REJECT */ if ( auth_ans->code != PW_AUTHENTICATION_ACK ) return SS$_INVLOGIN; /* ** Check digest of the received packet */ len = netiosb.iosb$w_bcnt; memcpy(vector,auth_ans->vector,AUTH_VECTOR_LEN); memcpy(auth_ans->vector,auth_req->vector,AUTH_VECTOR_LEN); md5_calc1(hash,ibuf,len,secret,secretlen); if ( status = memcmp(vector,hash,AUTH_VECTOR_LEN) ) { DEBUG("status = %u\n",status); return SS$_INVLOGIN; } *authstatus = SS$_NORMAL; DEBUG("astadr = %u\n",astadr); DEBUG("astprm = %u\n",astprm); if ( astadr ) return sys$dclast(astadr, astprm, 0); return SS$_NORMAL; } /* AUTHENTICATE */ /* * ROUTINE: ACCOUNTING * * DESCRIPTION: * Accounting callout, called by the SMTP server after a message is accepted from * the authenticated client. Note that this routine may be called multiple times * for each message -- once per recipient address. * * Note that this routine is optional; it is only called by the SMTP server * if it is provided by the installed authentication callout module. * * PARAMETERS: * ctxptr: (in) context block address (as returned by INIT routine), passed by reference * sessid: (in) session ID (as assigned by AUTHENTICATE routine), passed by reference * msgsize: (in) number of bytes in the message body, passed by reference * fromadr: (in) MAIL FROM: address, passed by descriptor * toadr: (in) RCPT TO: address, passed by descriptor * * RETURNS: VMS condition value * The caller ignores the returned value. */ unsigned int ACCOUNTING (void **ctxptr, const unsigned int *sessid, const unsigned int *msgsize, const struct dsc$descriptor *fromadr, const struct dsc$descriptor *toadr) { return SS$_NORMAL; } /* ACCOUNTING */ /* * ROUTINE: CLEANUP * * DESCRIPTION: * Called by the SMTP server to clean up after an authentication session. * This routine should free any resources that were allocated in the INIT, AUTHENTICATE, * or ACCOUNTING routines, including the context block. * * PARAMETERS: * ctxptr: (in/out) context block address (as returned by INIT routine), passed by reference * * RETURNS: VMS condition value */ unsigned int CLEANUP (void **ctxptr) { // return sys$dassgn (chan); } /* CLEANUP */