X-Git-Url: https://git.hcoop.net/hcoop/zz_old/modwaklog.git/blobdiff_plain/e21f34f04cb952253473f450a7e3fdaeb9eb75e8..f5b4deffca53c5fe1b9011b697bd17ee54b608e8:/mod_waklog.c diff --git a/mod_waklog.c b/mod_waklog.c dissimilarity index 65% index 81606e5..a5eba8c 100644 --- a/mod_waklog.c +++ b/mod_waklog.c @@ -1,639 +1,787 @@ -#include "httpd.h" -#include "http_config.h" -#include "http_conf_globals.h" -#include "http_log.h" -#include "http_protocol.h" -#include "http_request.h" -#include "http_core.h" -#include "ap_config.h" -#include - -#if defined(sun) -#include -#endif /* sun */ -#include -#include -#include -#include -#include -#include - -#include -#include - -#define KEYTAB_PATH "/home/drh/keytab.umweb.drhtest" -#define PRINCIPAL "umweb/drhtest" -#define AFS "afs" -#define IN_TKT_SERVICE "krbtgt/UMICH.EDU" - -#define K5PATH "FILE:/tmp/waklog.creds.k5" -#define K4PATH "/tmp/waklog.creds.k4" - -module waklog_module; - -struct ClearToken { - long AuthHandle; - char HandShakeKey[ 8 ]; - long ViceId; - long BeginTimestamp; - long EndTimestamp; -}; - -typedef struct { - int configured; - int protect; - char *keytab; - char *keytab_principal; - char *afs_instance; -} waklog_host_config; - -typedef struct { - struct ktc_token token; -} waklog_child_config; -waklog_child_config *child = NULL; - - - static void * -waklog_create_dir_config( pool *p, char *path ) -{ - waklog_host_config *cfg; - - cfg = (waklog_host_config *)ap_pcalloc( p, sizeof( waklog_host_config )); - cfg->configured = 0; - cfg->protect = 0; - cfg->keytab = 0; - cfg->keytab_principal = 0; - cfg->afs_instance = 0; - - return( cfg ); -} - - - static void * -waklog_create_server_config( pool *p, server_rec *s ) -{ - waklog_host_config *cfg; - - cfg = (waklog_host_config *)ap_pcalloc( p, sizeof( waklog_host_config )); - cfg->configured = 0; - cfg->protect = 0; - cfg->keytab = 0; - cfg->keytab_principal = 0; - cfg->afs_instance = 0; - - return( cfg ); -} - - - static const char * -set_waklog_protect( cmd_parms *params, void *mconfig, int flag ) -{ - waklog_host_config *cfg; - - if ( params->path == NULL ) { - cfg = (waklog_host_config *) ap_get_module_config( - params->server->module_config, &waklog_module ); - } else { - cfg = (waklog_host_config *)mconfig; - } - - cfg->protect = flag; - cfg->configured = 1; - return( NULL ); -} - - - static const char * -set_waklog_use_keytab( cmd_parms *params, void *mconfig, char *file ) -{ - waklog_host_config *cfg; - - if ( params->path == NULL ) { - cfg = (waklog_host_config *) ap_get_module_config( - params->server->module_config, &waklog_module ); - } else { - cfg = (waklog_host_config *)mconfig; - } - - ap_log_error( APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, params->server, - "mod_waklog: using keytab: %s", file ); - - cfg->keytab = file; - cfg->configured = 1; - return( NULL ); -} - - - static void -waklog_child_init( server_rec *s, pool *p ) -{ - - if ( child == NULL ) { - child = (waklog_child_config *) ap_palloc( p, sizeof( waklog_child_config ) ); - } - - memset( &child->token, 0, sizeof( struct ktc_token ) ); - - setpag(); - - return; -} - - -command_rec waklog_cmds[ ] = -{ - { "WaklogProtected", set_waklog_protect, - NULL, RSRC_CONF | ACCESS_CONF, FLAG, - "enable waklog on a location or directory basis" }, - - { "WaklogUseKeytab", set_waklog_use_keytab, - NULL, RSRC_CONF, TAKE1, - "Use the supplied keytab file rather than the user's TGT" }, - - { NULL } -}; - - - static void -pioctl_cleanup( void *data ) -{ - request_rec *r = (request_rec *)data; - - if ( child->token.ticketLen ) { - memset( &child->token, 0, sizeof( struct ktc_token ) ); - - ktc_ForgetAllTokens(); - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: ktc_ForgetAllTokens succeeded" ); - } - return; -} - - - static int -waklog_ktinit( server_rec *s ) -{ - krb5_error_code kerror; - krb5_context kcontext; - krb5_principal kprinc; - krb5_get_init_creds_opt kopts; - krb5_creds v5creds; - CREDENTIALS v4creds; - krb5_ccache kccache; - krb5_keytab keytab = 0; - char ktbuf[ MAX_KEYTAB_NAME_LEN + 1 ]; - krb5_timestamp now; - waklog_host_config *cfg; - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s, - "mod_waklog: waklog_ktinit called" ); - - if (( kerror = krb5_init_context( &kcontext ))) { - ap_log_error( APLOG_MARK, APLOG_ERR, s, - (char *)error_message( kerror )); - - goto cleanup1; - } - - /* use the path */ - if (( kerror = krb5_cc_resolve( kcontext, K5PATH, &kccache )) != 0 ) { - ap_log_error( APLOG_MARK, APLOG_ERR, s, - (char *)error_message( kerror )); - - goto cleanup2; - } - - if (( kerror = krb5_parse_name( kcontext, PRINCIPAL, &kprinc ))) { - ap_log_error( APLOG_MARK, APLOG_ERR, s, - (char *)error_message( kerror )); - - goto cleanup3; - } - - krb5_get_init_creds_opt_init( &kopts ); - krb5_get_init_creds_opt_set_tkt_life( &kopts, 10*60*60 ); - krb5_get_init_creds_opt_set_renew_life( &kopts, 0 ); - krb5_get_init_creds_opt_set_forwardable( &kopts, 1 ); - krb5_get_init_creds_opt_set_proxiable( &kopts, 0 ); - - cfg = (waklog_host_config *) ap_get_module_config( s->module_config, - &waklog_module ); - - /* which keytab should we use? */ - strcpy( ktbuf, cfg->keytab ? cfg->keytab : KEYTAB_PATH ); - - if ( strlen( ktbuf ) > MAX_KEYTAB_NAME_LEN ) { - ap_log_error( APLOG_MARK, APLOG_ERR, s, - "server configuration error" ); - - goto cleanup4; - } - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s, - "mod_waklog: waklog_ktinit using: %s", ktbuf ); - - if (( kerror = krb5_kt_resolve( kcontext, ktbuf, &keytab )) != 0 ) { - ap_log_error( APLOG_MARK, APLOG_ERR, s, - (char *)error_message( kerror )); - - goto cleanup4; - } - - /* get the krbtgt */ - if (( kerror = krb5_get_init_creds_keytab( kcontext, &v5creds, - kprinc, keytab, 0, IN_TKT_SERVICE, &kopts ))) { - - ap_log_error( APLOG_MARK, APLOG_ERR, s, - (char *)error_message( kerror )); - - goto cleanup5; - } - - if (( kerror = krb5_verify_init_creds( kcontext, &v5creds, - kprinc, keytab, NULL, NULL )) != 0 ) { - - ap_log_error( APLOG_MARK, APLOG_ERR, s, - (char *)error_message( kerror )); - - goto cleanup6; - } - - if (( kerror = krb5_cc_initialize( kcontext, kccache, kprinc )) != 0 ) { - ap_log_error( APLOG_MARK, APLOG_ERR, s, - (char *)error_message( kerror )); - - goto cleanup6; - } - - if (( kerror = krb5_cc_store_cred( kcontext, kccache, &v5creds )) != 0 ) { - ap_log_error( APLOG_MARK, APLOG_ERR, s, - (char *)error_message( kerror )); - - goto cleanup6; - } - -#if 0 - /* convert K5 => K4 */ - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s, - "mod_waklog: before krb524_convert_creds" ); - - if (( kerror = krb524_convert_creds_kdc( kcontext, - &v5creds, &v4creds )) != 0 ) { - - ap_log_error( APLOG_MARK, APLOG_ERR, s, - (char *)error_message( kerror )); - - goto cleanup6; - } - - /* use the path */ - krb_set_tkt_string( (char *)K4PATH ); - - /* initialize ticket cache */ - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s, - "mod_waklog: before in_tkt" ); - - if (( kerror = in_tkt( v4creds.pname, v4creds.pinst )) != KSUCCESS ) { - ap_log_error( APLOG_MARK, APLOG_ERR, s, - (char *)error_message( kerror )); - - goto cleanup6; - } - - /* stash, ticket, session key, etc for future use */ - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s, - "mod_waklog: before krb_save_credentials" ); - - if (( kerror = krb_save_credentials( v4creds.service, - v4creds.instance, v4creds.realm, v4creds.session, - v4creds.lifetime, v4creds.kvno, &(v4creds.ticket_st), - v4creds.issue_date )) != 0 ) { - - ap_log_error( APLOG_MARK, APLOG_ERR, s, - (char *)error_message( kerror )); - - goto cleanup6; - } -#endif /* 0 */ - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s, - "mod_waklog: waklog_ktinit success" ); - -cleanup6: if ( v5creds.client == kprinc ) { - v5creds.client = 0; - } - krb5_free_cred_contents( kcontext, &v5creds ); -cleanup5: (void)krb5_kt_close( kcontext, keytab ); -cleanup4: krb5_free_principal( kcontext, kprinc ); -cleanup3: krb5_cc_close( kcontext, kccache ); -cleanup2: krb5_free_context( kcontext ); -cleanup1: - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s, - "mod_waklog: waklog_ktinit: exiting" ); - - return( 0 ); -} - - - static void -waklog_aklog( request_rec *r ) -{ - int rc; - char buf[ 1024 ]; - const char *k4path = NULL; - const char *k5path = NULL; - krb5_error_code kerror; - krb5_context kcontext; - krb5_creds increds; - krb5_creds *v5credsp = NULL; - CREDENTIALS v4creds; - krb5_ccache kccache; - struct ktc_principal server = { "afs", "", "umich.edu" }; - struct ktc_principal client; - struct ktc_token token; - - k5path = ap_table_get( r->subprocess_env, "KRB5CCNAME" ); - k4path = ap_table_get( r->subprocess_env, "KRBTKFILE" ); - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: waklog_aklog called: k5path: %s, k4path: %s", k5path, k4path ); - - if ( !k5path || !k4path ) { - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: waklog_aklog giving up" ); - return; - } - - /* - ** Get/build creds from file/tgs, then see if we need to SetToken - */ - - if (( kerror = krb5_init_context( &kcontext ))) { - /* Authentication Required ( kerberos error ) */ - ap_log_error( APLOG_MARK, APLOG_ERR, r->server, - (char *)error_message( kerror )); - - return; - } - - memset( (char *)&increds, 0, sizeof(increds)); - - /* set server part */ - if (( kerror = krb5_parse_name( kcontext, AFS, &increds.server ))) { - ap_log_error( APLOG_MARK, APLOG_ERR, r->server, - (char *)error_message( kerror )); - - return; - } - - if (( kerror = krb5_cc_resolve( kcontext, k5path, &kccache )) != 0 ) { - ap_log_error( APLOG_MARK, APLOG_ERR, r->server, - (char *)error_message( kerror )); - - return; - } - - /* set client part */ - krb5_cc_get_principal( kcontext, kccache, &increds.client ); - - increds.times.endtime = 0; - /* Ask for DES since that is what V4 understands */ - increds.keyblock.enctype = ENCTYPE_DES_CBC_CRC; - - /* get the V5 credentials */ - if (( kerror = krb5_get_credentials( kcontext, 0, kccache, - &increds, &v5credsp ) ) ) { - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: krb5_get_credentials: %s", krb_err_txt[ kerror ] ); - return; - } - - /* get the V4 credentials */ - if (( kerror = krb524_convert_creds_kdc( kcontext, v5credsp, &v4creds ) ) ) { - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: krb524_convert_creds_kdc: %s", krb_err_txt[ kerror ] ); - return; - } - - /* assemble the token */ - token.kvno = v4creds.kvno; - token.startTime = v4creds.issue_date; - token.endTime = v5credsp->times.endtime; - memmove( &token.sessionKey, v4creds.session, 8 ); - token.ticketLen = v4creds.ticket_st.length ; - memmove( token.ticket, v4creds.ticket_st.dat, token.ticketLen ); - - /* make sure we have to do this */ - if ( child->token.kvno != token.kvno || - child->token.ticketLen != token.ticketLen || - memcmp( &child->token.sessionKey, &token.sessionKey, - sizeof( token.sessionKey ) ) || - memcmp( child->token.ticket, token.ticket, token.ticketLen ) ) { - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: %s.%s@%s", v4creds.service, v4creds.instance, - v4creds.realm ); - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: %d %d %d", v4creds.lifetime, v4creds.kvno, - v4creds.issue_date ); - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: %s %s", v4creds.pname, v4creds.pinst ); - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: %d", v4creds.ticket_st.length ); - - /* build the name */ - strcpy( buf, v4creds.pname ); - if ( v4creds.pinst[ 0 ] ) { - strcat( buf, "." ); - strcat( buf, v4creds.pinst ); - } - - /* assemble the client */ - strncpy( client.name, buf, MAXKTCNAMELEN - 1 ); - strcpy( client.instance, "" ); - strncpy( client.cell, v4creds.realm, MAXKTCNAMELEN - 1 ); - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: server: name=%s, instance=%s, cell=%s", - server.name, server.instance, server.cell ); - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: client: name=%s, instance=%s, cell=%s", - client.name, client.instance, client.cell ); - - /* use the path */ - krb_set_tkt_string( (char *)k4path ); - - /* rumor: we have to do this for AIX 4.1.4 with AFS 3.4+ */ - write( 2, "", 0 ); - - if ( ( rc = ktc_SetToken( &server, &token, &client, 0 ) ) ) { - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: settoken returned %d", rc ); - } - - /* save this */ - memmove( &child->token, &token, sizeof( struct ktc_token ) ); - - /* we'll need to unlog when this connection is done. */ - ap_register_cleanup( r->pool, (void *)r, pioctl_cleanup, ap_null_cleanup ); - } - - krb5_free_cred_contents( kcontext, v5credsp ); - krb5_free_principal( kcontext, increds.client ); - krb5_cc_close( kcontext, kccache ); - krb5_free_context( kcontext ); - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: finished with waklog_aklog" ); - -} - - static int -waklog_child_routine( void *s, child_info *pinfo ) -{ - ap_log_error( APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, s, - "mod_waklog: waklog_child_routine called" ); - - if ( !getuid() ) { - ap_log_error( APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, s, - "mod_waklog: waklog_child_routine called as root" ); - - /* this was causing the credential file to get owned by root */ - setgid(ap_group_id); - setuid(ap_user_id); - } - - while( 1 ) { - waklog_ktinit( s ); - sleep( 60 /* 10*60*60 - 5*60 */ ); - } - -} - - - static void -waklog_init( server_rec *s, pool *p ) -{ - extern char *version; - int pid; - - ap_log_error( APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, s, - "mod_waklog: version %s initialized.", version ); - - pid = ap_bspawn_child( p, waklog_child_routine, s, kill_always, - NULL, NULL, NULL ); - - ap_log_error( APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, s, - "mod_waklog: ap_bspawn_child: %d.", pid ); -} - - - static int -waklog_phase0( request_rec *r ) -{ - waklog_host_config *cfg; - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: phase0 called" ); - - /* directory config? */ - cfg = (waklog_host_config *)ap_get_module_config( - r->per_dir_config, &waklog_module); - - /* server config? */ - if ( !cfg->configured ) { - cfg = (waklog_host_config *)ap_get_module_config( - r->server->module_config, &waklog_module); - } - - if ( !cfg->protect ) { - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: phase0 declining" ); - return( DECLINED ); - } - - /* do this only if we are still unauthenticated */ - if ( !child->token.ticketLen ) { - - /* set our environment variables */ - ap_table_set( r->subprocess_env, "KRB5CCNAME", K5PATH ); - ap_table_set( r->subprocess_env, "KRBTKFILE", K4PATH ); - - /* stuff the credentials into the kernel */ - waklog_aklog( r ); - } - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: phase0 returning" ); - return DECLINED; -} - - - static int -waklog_phase7( request_rec *r ) -{ - waklog_host_config *cfg; - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: phase7 called" ); - - /* directory config? */ - cfg = (waklog_host_config *)ap_get_module_config( - r->per_dir_config, &waklog_module); - - /* server config? */ - if ( !cfg->configured ) { - cfg = (waklog_host_config *)ap_get_module_config( - r->server->module_config, &waklog_module); - } - - if ( !cfg->protect ) { - return( DECLINED ); - } - - /* stuff the credentials into the kernel */ - waklog_aklog( r ); - - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, - "mod_waklog: phase7 returning" ); - - return DECLINED; -} - - static void -waklog_new_connection( conn_rec *c ) { - ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, c->server, - "mod_waklog: new_connection called: conn_rec: 0x%08x pid: %d", c, getpid() ); - return; -} - -module MODULE_VAR_EXPORT waklog_module = { - STANDARD_MODULE_STUFF, - waklog_init, /* module initializer */ - waklog_create_dir_config, /* create per-dir config structures */ - NULL, /* merge per-dir config structures */ - waklog_create_server_config, /* create per-server config structures */ - NULL, /* merge per-server config structures */ - waklog_cmds, /* table of config file commands */ - NULL, /* [#8] MIME-typed-dispatched handlers */ - NULL, /* [#1] URI to filename translation */ - NULL, /* [#4] validate user id from request */ - NULL, /* [#5] check if the user is ok _here_ */ - NULL, /* [#3] check access by host address */ - NULL, /* [#6] determine MIME type */ - waklog_phase7, /* [#7] pre-run fixups */ - NULL, /* [#9] log a transaction */ - NULL, /* [#2] header parser */ - waklog_child_init, /* child_init */ - NULL, /* child_exit */ - waklog_phase0 /* [#0] post read-request */ -#ifdef EAPI - ,NULL, /* EAPI: add_module */ - NULL, /* EAPI: remove_module */ - NULL, /* EAPI: rewrite_command */ - waklog_new_connection /* EAPI: new_connection */ -#endif -}; +#define _LARGEFILE64_SOURCE + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "http_protocol.h" +#include "http_request.h" +#include "http_core.h" + +#ifdef STANDARD20_MODULE_STUFF +#include +#include +#include +#include + +module AP_MODULE_DECLARE_DATA waklog_module; + +#define MK_POOL apr_pool_t +#define MK_TABLE_GET apr_table_get +#include "unixd.h" +extern unixd_config_rec unixd_config; +#define ap_user_id unixd_config.user_id +#define ap_group_id unixd_config.group_id +#define ap_user_name unixd_config.user_name +#define command(name, func, var, type, usage) \ + AP_INIT_ ## type (name, (void*) func, \ + (void*)APR_OFFSETOF(waklog_config, var), \ + OR_AUTHCFG | RSRC_CONF, usage) +typedef struct { + int dummy; +} child_info; + +const char *userdata_key = "waklog_init"; +#else +#include "ap_config.h" + +module waklog_module; +#define MK_POOL pool +#define MK_TABLE_GET ap_table_get +#define command(name, func, var, type, usage) \ + { name, func, \ + (void*)XtOffsetOf(waklog_config, var), \ + OR_AUTHCFG | RSRC_CONF, type, usage } +#endif /* STANDARD20_MODULE_STUFF */ + +#define getModConfig(P, X) P = (waklog_host_config *) ap_get_module_config( (X)->module_config, &waklog_module ); + +#include + +#if defined(sun) +#include +#endif /* sun */ +#include +#include +#include +#include + +#define KEYTAB "/etc/keytab.wwwserver" +#define KEYTAB_PRINCIPAL "someplacewwwserver" +#define AFS_CELL "someplace.edu" + +#define TKT_LIFE 10*60*60 +#define SLEEP_TIME TKT_LIFE - 5*60 +/* If there's an error, retry more aggressively */ +#define ERR_SLEEP_TIME 5*60 + + +#define K5PATH "FILE:/tmp/waklog.creds.k5" + +typedef struct { + int forked; + int configured; + int protect; + char *keytab; + char *keytab_principal; + char *afs_cell; + MK_POOL *p; +} waklog_host_config; + +typedef struct { + struct ktc_token token; +} waklog_child_config; +waklog_child_config child; + +static void +log_error(const char *file, int line, int level, int status, + const server_rec *s, const char *fmt, ...) +{ + char errstr[1024]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(errstr, sizeof(errstr), fmt, ap); + va_end(ap); + +#ifdef STANDARD20_MODULE_STUFF + ap_log_error(file, line, level | APLOG_NOERRNO, status, s, "%s", errstr); +#else + ap_log_error(file, line, level | APLOG_NOERRNO, s, "%s", errstr); +#endif + +} + + static void * +waklog_create_server_config( MK_POOL *p, server_rec *s ) +{ + waklog_host_config *cfg; + + cfg = (waklog_host_config *)ap_pcalloc( p, sizeof( waklog_host_config )); + cfg->p = p; + cfg->forked = 0; + cfg->configured = 0; + cfg->protect = 0; + cfg->keytab = KEYTAB; + cfg->keytab_principal = KEYTAB_PRINCIPAL; + cfg->afs_cell = AFS_CELL; + + log_error( APLOG_MARK, APLOG_DEBUG, 0, s, "mod_waklog: server config created." ); + + return( cfg ); +} + + + static const char * +set_waklog_protect( cmd_parms *params, void *mconfig, int flag ) +{ + waklog_host_config *cfg; + + getModConfig(cfg, params->server ); + + cfg->protect = flag; + cfg->configured = 1; + log_error( APLOG_MARK, APLOG_DEBUG, 0, params->server, "mod_waklog: waklog_protect set" ); + return( NULL ); +} + + + static const char * +set_waklog_keytab( cmd_parms *params, void *mconfig, char *file ) +{ + waklog_host_config *cfg; + + getModConfig(cfg, params->server ); + + log_error( APLOG_MARK, APLOG_INFO, 0, params->server, + "mod_waklog: will use keytab: %s", file ); + + cfg->keytab = ap_pstrdup ( params->pool, file ); + cfg->configured = 1; + return( NULL ); +} + + + static const char * +set_waklog_use_keytab_principal( cmd_parms *params, void *mconfig, char *file ) +{ + waklog_host_config *cfg; + + getModConfig(cfg, params->server ); + + log_error( APLOG_MARK, APLOG_INFO, 0, params->server, + "mod_waklog: will use keytab_principal: %s", file ); + + cfg->keytab_principal = ap_pstrdup ( params->pool, file ); + cfg->configured = 1; + return( NULL ); +} + + + static const char * +set_waklog_use_afs_cell( cmd_parms *params, void *mconfig, char *file ) +{ + waklog_host_config *cfg; + + getModConfig(cfg, params->server ); + + log_error( APLOG_MARK, APLOG_INFO, 0, params->server, + "mod_waklog: will use afs_cell: %s", file ); + + cfg->afs_cell = ap_pstrdup( params->pool, file ); + cfg->configured = 1; + return( NULL ); +} + + + static void +#ifdef STANDARD20_MODULE_STUFF +waklog_child_init(MK_POOL *p, server_rec *s) +#else +waklog_child_init(server_rec *s, MK_POOL *p) +#endif +{ + + log_error( APLOG_MARK, APLOG_DEBUG, 0, s, + "mod_waklog: child_init called" ); + + memset( &child.token, 0, sizeof( struct ktc_token ) ); + + setpag(); + + log_error( APLOG_MARK, APLOG_DEBUG, 0, s, + "mod_waklog: child_init returned" ); + + return; +} + +typedef struct { + int wak_protect; + char *wak_keytab; + char *wak_ktprinc; + char *wak_afscell; +} waklog_config; + +command_rec waklog_cmds[ ] = +{ + command("WaklogProtected", set_waklog_protect, wak_protect, FLAG, "enable waklog on a location or directory basis"), + + command("WaklogKeytab", set_waklog_keytab, wak_keytab, TAKE1, "Use the supplied keytab rather than the default"), + + command("WaklogUseKeytabPrincipal", set_waklog_use_keytab_principal, wak_ktprinc, TAKE1, "Use the supplied keytab principal rather than the default"), + + command("WaklogUseAFSCell", set_waklog_use_afs_cell, wak_afscell, TAKE1, "Use the supplied AFS cell rather than the default"), + + { NULL } +}; + + + static int +token_cleanup( void *data ) +{ + request_rec *r = (request_rec *)data; + + if ( child.token.ticketLen ) { + memset( &child.token, 0, sizeof( struct ktc_token ) ); + + ktc_ForgetAllTokens(); + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: ktc_ForgetAllTokens succeeded: pid: %d", getpid() ); + } + return 0; +} + + + static int +waklog_kinit( server_rec *s ) +{ + krb5_error_code kerror = 0; + krb5_context kcontext = NULL; + krb5_principal kprinc = NULL; + krb5_get_init_creds_opt kopts; + krb5_creds v5creds; + krb5_ccache kccache = NULL; + krb5_keytab keytab = NULL; + char ktbuf[ MAX_KEYTAB_NAME_LEN + 1 ]; + int i; + waklog_host_config *cfg; + + log_error( APLOG_MARK, APLOG_DEBUG, 0, s, + "mod_waklog: waklog_kinit called: pid: %d", getpid() ); + + getModConfig(cfg, s); + + if (( kerror = krb5_init_context( &kcontext ))) { + log_error( APLOG_MARK, APLOG_ERR, 0, s, + "mod_waklog: %s", (char *)error_message( kerror )); + + goto cleanup; + } + + /* use the path */ + if (( kerror = krb5_cc_resolve( kcontext, K5PATH, &kccache )) != 0 ) { + log_error( APLOG_MARK, APLOG_ERR, 0, s, + "mod_waklog: %s", (char *)error_message( kerror )); + + goto cleanup; + } + + log_error( APLOG_MARK, APLOG_DEBUG, 0, s, + "mod_waklog: keytab_principal: %s", cfg->keytab_principal ); + + if (( kerror = krb5_parse_name( kcontext, cfg->keytab_principal, &kprinc ))) { + log_error( APLOG_MARK, APLOG_ERR, 0, s, + "mod_waklog: %s", (char *)error_message( kerror )); + + goto cleanup; + } + + krb5_get_init_creds_opt_init( &kopts ); + krb5_get_init_creds_opt_set_tkt_life( &kopts, TKT_LIFE ); + krb5_get_init_creds_opt_set_renew_life( &kopts, 0 ); + krb5_get_init_creds_opt_set_forwardable( &kopts, 1 ); + krb5_get_init_creds_opt_set_proxiable( &kopts, 0 ); + + /* keytab from config */ + strncpy( ktbuf, cfg->keytab, sizeof( ktbuf ) - 1 ); + + log_error( APLOG_MARK, APLOG_DEBUG, 0, s, + "mod_waklog: waklog_kinit using: %s", ktbuf ); + + if (( kerror = krb5_kt_resolve( kcontext, ktbuf, &keytab )) != 0 ) { + log_error( APLOG_MARK, APLOG_ERR, 0, s, + "mod_waklog:krb5_kt_resolve %s", (char *)error_message( kerror )); + + goto cleanup; + } + + memset( (char *)&v5creds, 0, sizeof(v5creds)); + + /* get the krbtgt */ + if (( kerror = krb5_get_init_creds_keytab( kcontext, &v5creds, + kprinc, keytab, 0, NULL, &kopts ))) { + + log_error( APLOG_MARK, APLOG_ERR, 0, s, + "mod_waklog:krb5_get_init_creds_keytab %s", (char *)error_message( kerror )); + + goto cleanup; + } + + if (( kerror = krb5_cc_initialize( kcontext, kccache, kprinc )) != 0 ) { + log_error( APLOG_MARK, APLOG_ERR, 0, s, + "mod_waklog:krb5_cc_initialize %s", (char *)error_message( kerror )); + + goto cleanup; + } + + kerror = krb5_cc_store_cred( kcontext, kccache, &v5creds ); + krb5_free_cred_contents( kcontext, &v5creds ); + if ( kerror != 0 ) { + log_error( APLOG_MARK, APLOG_ERR, 0, s, + "mod_waklog: %s", (char *)error_message( kerror )); + + goto cleanup; + } + + log_error( APLOG_MARK, APLOG_DEBUG, 0, s, + "mod_waklog: waklog_kinit success" ); + +cleanup: + if ( keytab ) + (void)krb5_kt_close( kcontext, keytab ); + if ( kprinc ) + krb5_free_principal( kcontext, kprinc ); + if ( kccache ) + krb5_cc_close( kcontext, kccache ); + if ( kcontext ) + krb5_free_context( kcontext ); + + log_error( APLOG_MARK, APLOG_DEBUG, 0, s, + "mod_waklog: waklog_kinit: exiting" ); + + return( kerror ); +} + + + static void +waklog_aklog( request_rec *r ) +{ + int rc; + char buf[ MAXKTCTICKETLEN ]; + const char *k5path = NULL; + krb5_error_code kerror; + krb5_context kcontext = NULL; + krb5_creds increds; + krb5_creds *v5credsp = NULL; + krb5_ccache kccache = NULL; + struct ktc_principal server = { "afs", "", "" }; + struct ktc_principal client; + struct ktc_token token; + waklog_host_config *cfg; + int buflen; + + k5path = MK_TABLE_GET( r->subprocess_env, "KRB5CCNAME" ); + + log_error( APLOG_MARK, APLOG_INFO, 0, r->server, + "mod_waklog: waklog_aklog called: k5path: %s", k5path ); + + if ( k5path == NULL ) { + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: waklog_aklog giving up" ); + goto cleanup; + } + + /* + ** Get/build creds from file/tgs, then see if we need to SetToken + */ + + if (( kerror = krb5_init_context( &kcontext ))) { + /* Authentication Required ( kerberos error ) */ + log_error( APLOG_MARK, APLOG_ERR, 0, r->server, + (char *)error_message( kerror )); + + goto cleanup; + } + + memset( (char *)&increds, 0, sizeof(increds)); + + getModConfig(cfg, r->server ); + + /* afs/ or afs */ + strncpy( buf, "afs", sizeof( buf ) - 1 ); + if ( strcmp( cfg->afs_cell, AFS_CELL ) ) { + strncat( buf, "/" , sizeof( buf ) - strlen( buf ) - 1 ); + strncat( buf, cfg->afs_cell, sizeof( buf ) - strlen( buf ) - 1 ); + } + + /* set server part */ + if (( kerror = krb5_parse_name( kcontext, buf, &increds.server ))) { + log_error( APLOG_MARK, APLOG_ERR, 0, r->server, + (char *)error_message( kerror )); + + goto cleanup; + } + + if (( kerror = krb5_cc_resolve( kcontext, k5path, &kccache )) != 0 ) { + log_error( APLOG_MARK, APLOG_ERR, 0, r->server, + (char *)error_message( kerror )); + + goto cleanup; + } + + /* set client part */ + krb5_cc_get_principal( kcontext, kccache, &increds.client ); + + increds.times.endtime = 0; + /* Ask for DES since that is what V4 understands */ + increds.keyblock.enctype = ENCTYPE_DES_CBC_CRC; + + /* get the V5 credentials */ + if (( kerror = krb5_get_credentials( kcontext, 0, kccache, + &increds, &v5credsp ) ) ) { + log_error( APLOG_MARK, APLOG_ERR, 0, r->server, + "mod_waklog: krb5_get_credentials: %s", error_message( kerror )); + goto cleanup; + } + + /* don't overflow */ + if ( v5credsp->ticket.length >= MAXKTCTICKETLEN ) { /* from krb524d.c */ + log_error( APLOG_MARK, APLOG_ERR, 0, r->server, + "mod_waklog: ticket size (%d) too big to fake", v5credsp->ticket.length ); + goto cleanup; + } + + /* assemble the token */ + memset( &token, 0, sizeof( struct ktc_token ) ); + + token.startTime = v5credsp->times.starttime ? v5credsp->times.starttime : v5credsp->times.authtime; + token.endTime = v5credsp->times.endtime; + memmove( &token.sessionKey, v5credsp->keyblock.contents, v5credsp->keyblock.length ); + token.kvno = RXKAD_TKT_TYPE_KERBEROS_V5; + token.ticketLen = v5credsp->ticket.length; + memmove( token.ticket, v5credsp->ticket.data, token.ticketLen ); + + /* make sure we have to do this */ + if ( child.token.kvno != token.kvno || + child.token.ticketLen != token.ticketLen || + (memcmp( &child.token.sessionKey, &token.sessionKey, + sizeof( token.sessionKey ) )) || + (memcmp( child.token.ticket, token.ticket, token.ticketLen )) ) { + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: client: %s", buf ); + + /* build the name */ + memmove( buf, v5credsp->client->data[0].data, + min( v5credsp->client->data[0].length, MAXKTCNAMELEN - 1 ) ); + buf[ v5credsp->client->data[0].length ] = '\0'; + if ( v5credsp->client->length > 1 ) { + strncat( buf, ".", sizeof( buf ) - strlen( buf ) - 1 ); + buflen = strlen( buf ); + memmove( buf + buflen, v5credsp->client->data[1].data, + min( v5credsp->client->data[1].length, MAXKTCNAMELEN - strlen( buf ) - 1 ) ); + buf[ buflen + v5credsp->client->data[1].length ] = '\0'; + } + + /* assemble the client */ + strncpy( client.name, buf, sizeof( client.name ) - 1 ); + strncpy( client.instance, "", sizeof( client.instance) - 1 ); + memmove( buf, v5credsp->client->realm.data, + min( v5credsp->client->realm.length, MAXKTCNAMELEN - 1 ) ); + buf[ v5credsp->client->realm.length ] = '\0'; + strncpy( client.cell, buf, sizeof( client.cell ) - 1 ); + + /* assemble the server's cell */ + strncpy( server.cell, cfg->afs_cell , sizeof( server.cell ) - 1 ); + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: server: name=%s, instance=%s, cell=%s", + server.name, server.instance, server.cell ); + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: client: name=%s, instance=%s, cell=%s", + client.name, client.instance, client.cell ); + + /* use the path */ + + /* rumor: we have to do this for AIX 4.1.4 with AFS 3.4+ */ + write( 2, "", 0 ); + + if ( ( rc = ktc_SetToken( &server, &token, &client, 0 ) ) ) { + log_error( APLOG_MARK, APLOG_ERR, 0, r->server, + "mod_waklog: settoken returned %d", rc ); + goto cleanup; + } + + /* save this */ + memmove( &child.token, &token, sizeof( struct ktc_token ) ); + + /* we'll need to unlog when this connection is done. */ + ap_register_cleanup( r->pool, (void *)r, token_cleanup, ap_null_cleanup ); + } + +cleanup: + if ( v5credsp ) + krb5_free_cred_contents( kcontext, v5credsp ); + if ( increds.client ) + krb5_free_principal( kcontext, increds.client ); + if ( increds.server ) + krb5_free_principal( kcontext, increds.server ); + if ( kccache ) + krb5_cc_close( kcontext, kccache ); + if ( kcontext ) + krb5_free_context( kcontext ); + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: finished with waklog_aklog" ); + + return; + +} + + static int +waklog_child_routine( void *s, child_info *pinfo ) +{ + if ( !getuid() ) { + log_error( APLOG_MARK, APLOG_DEBUG, 0, s, + "mod_waklog: waklog_child_routine called as root" ); + + /* this was causing the credential file to get owned by root */ + setgid(ap_group_id); + setuid(ap_user_id); + } + + while( 1 ) { + waklog_kinit( s ); + log_error( APLOG_MARK, APLOG_DEBUG, 0, s, + "mod_waklog: child_routine sleeping" ); + sleep( SLEEP_TIME ); + log_error( APLOG_MARK, APLOG_DEBUG, 0, s, + "mod_waklog: slept, calling waklog_kinit" ); + } + +} + +#ifdef STANDARD20_MODULE_STUFF +static int +waklog_init_handler(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + int rv; + extern char *version; + apr_proc_t *proc; + waklog_host_config *cfg; + void *data; + + getModConfig(cfg, s); + + /* initialize_module() will be called twice, and if it's a DSO + * then all static data from the first call will be lost. Only + * set up our static data on the second call. + * see http://issues.apache.org/bugzilla/show_bug.cgi?id=37519 */ + apr_pool_userdata_get(&data, userdata_key, s->process->pool); + + if (!data) { + apr_pool_userdata_set((const void *)1, userdata_key, + apr_pool_cleanup_null, s->process->pool); + } else { + log_error( APLOG_MARK, APLOG_INFO, 0, s, + "mod_waklog: version %s initialized.", version ); + + proc = (apr_proc_t *)ap_pcalloc( s->process->pool, sizeof(apr_proc_t)); + + rv = apr_proc_fork(proc, s->process->pool); + + if (rv == APR_INCHILD) { + waklog_child_routine(s, NULL); + } else { + apr_pool_note_subprocess(s->process->pool, proc, APR_KILL_ALWAYS); + } + /* parent and child */ + cfg->forked = proc->pid; + } + return 0; +} +#else + static void +waklog_init( server_rec *s, MK_POOL *p ) +{ + extern char *version; + int pid; + + log_error( APLOG_MARK, APLOG_INFO, 0, s, + "mod_waklog: version %s initialized.", version ); + + pid = ap_bspawn_child( p, waklog_child_routine, s, kill_always, + NULL, NULL, NULL ); + + log_error( APLOG_MARK, APLOG_DEBUG, 0, s, + "mod_waklog: ap_bspawn_child: %d.", pid ); +} +#endif + + static int +waklog_phase0( request_rec *r ) +{ + waklog_host_config *cfg; + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: phase0 called" ); + + getModConfig(cfg, r->server ); + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: phase0, checking cfg->protect" ); + if ( !cfg->protect ) { + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: phase0 declining" ); + return( DECLINED ); + } + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: phase0, NOT setting environment variable" ); + /* set our environment variable */ + apr_table_set( r->subprocess_env, "KRB5CCNAME", K5PATH ); + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: phase0, checking child.token.ticketLen" ); + /* do this only if we are still unauthenticated */ + if ( !child.token.ticketLen ) { + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: phase0, calling waklog_aklog" ); + /* stuff the credentials into the kernel */ + waklog_aklog( r ); + } + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: phase0 returning" ); + return DECLINED; +} + + + static int +waklog_phase7( request_rec *r ) +{ + waklog_host_config *cfg; + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: phase7 called" ); + + getModConfig(cfg, r->server ); + + if ( !cfg->protect ) { + return( DECLINED ); + } + + /* stuff the credentials into the kernel */ + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: phase7, calling waklog_aklog" ); + waklog_aklog( r ); + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: phase7 returning" ); + + return DECLINED; +} + +static +#ifdef STANDARD20_MODULE_STUFF +int +#else +void +#endif +waklog_new_connection( conn_rec *c +#ifdef STANDARD20_MODULE_STUFF + , void *dummy +#endif + ) { + log_error( APLOG_MARK, APLOG_DEBUG, 0, c->base_server, + "mod_waklog: new_connection called: pid: %d", getpid() ); + return +#ifdef STANDARD20_MODULE_STUFF + 0 +#endif + ; +} + + +/* +** Here's a quick explaination for phase0 and phase2: +** Apache does a stat() on the path between phase0 and +** phase2, and must by ACLed rl to succeed. So, at +** phase0 we acquire credentials for umweb:servers from +** a keytab, and at phase2 we must ensure we remove them. +** +** Failure to "unlog" would be a security risk. +*/ + static int +waklog_phase2( request_rec *r ) +{ + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: phase2 called" ); + + if ( child.token.ticketLen ) { + memset( &child.token, 0, sizeof( struct ktc_token ) ); + + ktc_ForgetAllTokens(); + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: ktc_ForgetAllTokens succeeded: pid: %d", getpid() ); + } + + log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mod_waklog: phase2 returning" ); + + return DECLINED; +} + +#ifndef STANDARD20_MODULE_STUFF +module MODULE_VAR_EXPORT waklog_module = { + STANDARD_MODULE_STUFF, + waklog_init, /* module initializer */ +#if 0 + waklog_create_dir_config, /* create per-dir config structures */ +#else /* 0 */ + NULL, /* create per-dir config structures */ +#endif /* 0 */ + NULL, /* merge per-dir config structures */ + waklog_create_server_config, /* create per-server config structures */ + NULL, /* merge per-server config structures */ + waklog_cmds, /* table of config file commands */ + NULL, /* [#8] MIME-typed-dispatched handlers */ + NULL, /* [#1] URI to filename translation */ + NULL, /* [#4] validate user id from request */ + NULL, /* [#5] check if the user is ok _here_ */ + NULL, /* [#3] check access by host address */ + NULL, /* [#6] determine MIME type */ + waklog_phase7, /* [#7] pre-run fixups */ + NULL, /* [#9] log a transaction */ + waklog_phase2, /* [#2] header parser */ + waklog_child_init, /* child_init */ + NULL, /* child_exit */ + waklog_phase0 /* [#0] post read-request */ +#ifdef EAPI + ,NULL, /* EAPI: add_module */ + NULL, /* EAPI: remove_module */ + NULL, /* EAPI: rewrite_command */ + waklog_new_connection /* EAPI: new_connection */ +#endif +}; +#else +static void +waklog_register_hooks(apr_pool_t *p) +{ + ap_hook_fixups(waklog_phase7, NULL, NULL, APR_HOOK_FIRST); + ap_hook_header_parser(waklog_phase2, NULL, NULL, APR_HOOK_FIRST); + ap_hook_child_init(waklog_child_init, NULL, NULL, APR_HOOK_FIRST); + ap_hook_post_read_request(waklog_phase0, NULL, NULL, APR_HOOK_FIRST); + ap_hook_pre_connection(waklog_new_connection, NULL, NULL, APR_HOOK_FIRST); + ap_hook_post_config(waklog_init_handler, NULL, NULL, APR_HOOK_MIDDLE); +} + + +module AP_MODULE_DECLARE_DATA waklog_module = +{ + STANDARD20_MODULE_STUFF, + NULL, /* create per-dir conf structures */ + NULL, /* merge per-dir conf structures */ + waklog_create_server_config, /* create per-server conf structures */ + NULL, /* merge per-server conf structures */ + waklog_cmds, /* table of configuration directives */ + waklog_register_hooks /* register hooks */ +}; +#endif +