/* * auth_callout_example.c * * Example of an authentication callout for use with the MX SMTP server. * Copyright (c) 2008, Matthew Madison. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of the copyright owner nor the names of any other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * 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 AUTH_CALLOUT_EXAMPLE * $ LINK/NOTRACE/SHARE AUTH_CALLOUT_EXAMPLE.OBJ, SYS$INPUT:/OPTION * UNIVERSAL=INIT,AUTHENTICATE,ACCOUNTING,CLEANUP * ^Z ! (ctrl/Z) * * Building the callout module (Alpha): * * $ CC AUTH_CALLOUT_EXAMPLE * $ LINK/NOTRACE/SHARE AUTH_CALLOUT_EXAMPLE.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]AUTH_CALLOUT_EXAMPLE.EXE * $ MCP SET SMTP/AUTHENTICATION=PLAIN * $ MCP SHUTDOWN SMTP_SERVER * $ @SYS$STARTUP:MX_STARTUP SMTP_SERVER */ #include #include #include #include #include #include #include #include #define OK(status_) $VMS_STATUS_SUCCESS(status_) /* * Context structure used to track a single session */ typedef unsigned int (*ast_routine_t)(void *astprm); typedef struct { unsigned int placeholder; ast_routine_t astadr; void *astprm; unsigned int *authstatus; unsigned int *sessid; int success; } auth_context_t; static const unsigned int context_size = sizeof(auth_context_t); /* * Forward declarations */ unsigned int INIT(void **ctxptr); unsigned int AUTHENTICATE(void **ctxptr, const struct dsc$descriptor *usrnam, const struct dsc$descriptor *pass, const struct sockaddr *cliaddr, int cliaddrlen, unsigned int *sessid, unsigned int *authstatus, ast_routine_t astadr, void *astprm); static unsigned int auth_ast(void *astprm); unsigned int ACCOUNTING(void **ctxptr, const unsigned int *sessid, const unsigned int *msgsize, const struct dsc$descriptor *fromadr, const struct dsc$descriptor *toadr); unsigned int CLEANUP(void **ctxptr); /* * 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) { auth_context_t *ctx; unsigned int status; status = lib$get_vm (&context_size, &ctx); if (OK(status)) { memset(ctx, 0, sizeof(*ctx)); *ctxptr = ctx; } return status; } /* INIT */ /* * 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 *pass, const struct sockaddr *cliaddr, int cliaddrlen, unsigned int *sessid, unsigned int *authstatus, ast_routine_t astadr, void *astprm) { auth_context_t *ctx = *ctxptr; unsigned int status; ctx->authstatus = authstatus; ctx->sessid = sessid; ctx->astadr = astadr; ctx->astprm = astprm; /* * Even though we don't perform any I/O here, we use the AST completion mechanism * for demonstration purposes. Set the "success" context field for later use by * our AST completion routine. */ ctx->success = 0; if (usrnam->dsc$w_length == 6 && memcmp(usrnam->dsc$a_pointer, "Farfel", 6) == 0) if (pass->dsc$w_length == 6 && memcmp(pass->dsc$a_pointer, "1A2b3C", 6) == 0) ctx->success = 1; status = sys$dclast(auth_ast, ctx, 0); /* * If the AST isn't going to fire, then we should set the authentication * status in this routine (to a failure value). * * Note that if this routine normally completes synchronously, then it should * always set the authstatus argument to the appropriate success/failure status * and should always return a failure status. The returned status only indicates * to the caller whether or not asynchronous completion is being used; it's the * authstatus argument that indicates the success or failure of the authentication itself. */ if (!OK(status)) *authstatus = SS$_INVLOGIN; return status; } /* AUTHENTICATE */ /* * ROUTINE: auth_ast * * DESCRIPTION: * Sample AST completion routine for AUTHENTICATE. * * PARAMETERS: * astprm: address of our context, passed by value * * RETURNS: VMS condition value * Always returns SS$_NORMAL (returned value is actually ignored)j */ static unsigned int auth_ast (void *astprm) { auth_context_t *ctx = astprm; static unsigned int session_id_counter = 0; /* * For a successful authentication, we assign a session ID which will be * included in the Received: header of any message sent by the client and will * be provided to the ACCOUNTING routine. For this example, we simply * use a monotonically increasing integer; however, it can be any valid 32-bit value. * If you set the session ID to zero, however, the SMTP server assumes that it should * not be included in the Received: header. */ if (ctx->success) { *ctx->authstatus = SS$_NORMAL; *ctx->sessid = ++session_id_counter; } else *ctx->authstatus = SS$_INVLOGIN; /* * Now inform the caller that the I/O has completed */ return (*ctx->astadr)(ctx->astprm); } /* auth_ast */ /* * 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) { static $DESCRIPTOR(ctrstr, "!%D ID=!UL SIZE=!UL FROM=!AS TO=!AS"); struct dsc$descriptor dsc; char buf[512]; unsigned int status; dsc.dsc$b_class = DSC$K_CLASS_S; dsc.dsc$b_dtype = DSC$K_DTYPE_T; dsc.dsc$w_length = sizeof(buf); dsc.dsc$a_pointer = buf; status = sys$fao(&ctrstr, &dsc.dsc$w_length, &dsc, 0, *sessid, *msgsize, fromadr, toadr); if (OK(status)) lib$put_output(&dsc); return status; } /* 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) { unsigned int status; lib$free_vm (&context_size, ctxptr); *ctxptr = 0; /* not required, but just to be safe */ return SS$_NORMAL; } /* CLEANUP */