/* ** mod_authn_myproxy.c -- authenticates against MyProxy ** ** $Id: mod_authn_myproxy.c 423 2010-09-13 10:20:37Z toza $ ** ** See http://myproxy.ncsa.uiuc.edu/apache ** */ #include "httpd.h" #include "http_config.h" #include "http_log.h" #include "http_protocol.h" #include "ap_config.h" #include "ap_provider.h" #include "ap_mpm.h" #include "apr_strings.h" #include "apr_sha1.h" #include "apr_date.h" #include "apr_time.h" #include "apr_file_io.h" #include "apr_file_info.h" #include "mod_auth.h" #include #include #include #include #include #include #include #define AUTHN_MYPROXY_VERSION "2" /* Peter Gutmann's "X.509 Style Guide" has this to say on * the length of a distinguished name: * * There is nothing in any of these standards that would * prevent me from including a 1 gigabit MPEG movie of me * playing with my cat as one of the RDN components of the * DN in my certificate. -- Bob Jueneman on IETF-PKIX * * We will use a more sane limit. */ #define AUTHN_MYPROXY_MAX_DN_LEN 384 #define AUTHN_MYPROXY_SALT_SIZE 8 #define AUTHN_MYPROXY_DEFAULT_USE_GRIDMAP 0 #define AUTHN_MYPROXY_DEFAULT_SET_LOGNAME 0 #define AUTHN_MYPROXY_DEFAULT_REQUESTED_LIFETIME 120 #define AUTHN_MYPROXY_DEFAULT_MIN_LIFETIME 10 #define AUTHN_MYPROXY_DEFAULT_MYPROXY_PORT MYPROXY_SERVER_PORT #define AUTHN_MYPROXY_DEFAULT_VOMS_PORT 15000 #define AUTHN_MYPROXY_LOCK_TRIES 30 #define AUTHN_MYPROXY_LOCK_SLEEP 500000 /* in usec */ module AP_MODULE_DECLARE_DATA authn_myproxy_module; /* per-server configuration data */ typedef struct authn_myproxy_server_conf { char *cache_dir; int module_failed; } authn_myproxy_server_conf_t; /* per-directory configuration data */ typedef struct authn_myproxy_dir_conf { int use_gridmap; int set_logname; int requested_lifetime; /* in minutes */ int min_lifetime; /* in minutes */ char *myproxy_server; int myproxy_port; /* XXX signed to match MyProxy... * Perhaps these should be unsigned? */ char *voname; /* name of vo */ char *voms_server; /* voms server hostname */ int voms_port; char *voms_server_host_dn; } authn_myproxy_dir_conf_t; /* holds both per-server and per-directory configuration */ typedef struct authn_myproxy_conf { authn_myproxy_server_conf_t *server_conf; authn_myproxy_dir_conf_t *dir_conf; } authn_myproxy_conf_t; /* certificate information */ typedef struct authn_myproxy_cert { char *file; /* should be same as get_user_filename() */ char *identity; /* DN of identity this cert represents */ apr_time_t good_till; } authn_myproxy_cert_t; /* cache record */ typedef struct authn_myproxy_cache { char *user; char *salt; char *passphrase_hash; char *myproxy_server; unsigned int myproxy_port; unsigned int requested_lifetime; authn_myproxy_cert_t *cert; char *voname; } authn_myproxy_cache_t; /* allows us to use APR memory management with MyProxy */ typedef struct { myproxy_socket_attrs_t *socket_attrs; myproxy_request_t *request; myproxy_response_t *response; } authn_myproxy_conn_t; /* from strmd5() in myproxy_creds.c */ static char *get_hex(apr_pool_t *p, const unsigned char *input, int length) { int i; char *buf; AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(input != NULL); /* each byte becomes two hex digits (and then a trailing \0) */ buf = apr_palloc(p, 2 * length + 1); for (i = 0; i < length; i++) { int dd = input[i] & 0x0f; buf[2*i+1] = dd<10 ? dd+'0' : dd-10+'a'; dd = input[i] >> 4; buf[2*i] = dd<10 ? dd+'0' : dd-10+'a'; } buf[2 * length] = '\0'; return buf; } static char *get_hash(apr_pool_t *p, const char *input) { unsigned char sha1[APR_SHA1_DIGESTSIZE]; apr_sha1_ctx_t ctx; AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(input != NULL); apr_sha1_init(&ctx); apr_sha1_update(&ctx, input, strlen(input)); apr_sha1_final(sha1, &ctx); return get_hex(p, sha1, APR_SHA1_DIGESTSIZE); } static int is_cert_current(const authn_myproxy_conf_t *conf_rec, const authn_myproxy_cert_t *cert_rec) { AP_DEBUG_ASSERT(conf_rec != NULL); AP_DEBUG_ASSERT(cert_rec != NULL); AP_DEBUG_ASSERT(conf_rec->dir_conf != NULL); /* Check to make sure the certificate is currently valid, * and that it is valid long enough to be useful. * * Previously, I said: * We could also check to see if the lifetime is *greater* * than the lifetime we requested -- but that's not a good * idea as it rules out multiple instances with unequal * lifetimes sharing a single cache. (It would eventually * bring the lifetime of all cached certificates down to * the minimum of all the lifetimes.) * * At the time, the cache directory was user-configurable. * That has changed; it is now set by the administrator. The * cache is shared by sites that may have differing lifetime * settings. The practical effect of this is that a user who * logs on via site A and receives a ten-hour delegation may * later log onto site B which is configured for a fifteen- * minute delegation. The applications on site B expect a * short delegation but actually get the long delegation * that was originally retrieved for site A. * * This is probably not good! How can we fix this? Well, we * we can include the requested lifetime in the cache record; * if we retrieve a record and find it has a requested lifetime * that is different from our policy, we can silently drop the * record and later replace it with one of our own. * * But, wait -- this means that if a user is browsing site A * and site B simultaneously, each site will repeatedly replace * the cached credential with a fresh one conforming to its * own expectation of how long the lifetime should be. * * The fix is to incorporate the lifetime in the filename! We * already include the MyProxy server name and port number to * allow the same user to have a record for multiple servers; * we'll just extend it to keep track of the lifetime, too. * * Our check, then, is: "the certificate must be valid * ${min_lifetime} minutes from now" */ if (apr_time_now() + apr_time_from_sec(conf_rec->dir_conf->min_lifetime * 60) <= cert_rec->good_till) return 1; /* otherwise, failed */ return 0; } static int check_gridmap(const authn_myproxy_cache_t *cache_rec) { AP_DEBUG_ASSERT(cache_rec != NULL); AP_DEBUG_ASSERT(cache_rec->cert != NULL); AP_DEBUG_ASSERT(cache_rec->cert->identity != NULL); AP_DEBUG_ASSERT(cache_rec->user != NULL); /* at this point, Globus modules have been initialized */ return (globus_gss_assist_userok(cache_rec->cert->identity, cache_rec->user) == 0); } static char *get_user_hash_for_filename(apr_pool_t *p, const authn_myproxy_conf_t *conf_rec, const char *user) { const char *input; AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(conf_rec != NULL); AP_DEBUG_ASSERT(user != NULL); AP_DEBUG_ASSERT(conf_rec->dir_conf != NULL); AP_DEBUG_ASSERT(conf_rec->dir_conf->myproxy_server != NULL); /* see the comment in is_cert_current() */ if (conf_rec->dir_conf->voname != NULL) { input = apr_psprintf(p, "%s:%d:%d:%s:%s", conf_rec->dir_conf->myproxy_server, conf_rec->dir_conf->myproxy_port, conf_rec->dir_conf->requested_lifetime, user, conf_rec->dir_conf->voname); } else { input = apr_psprintf(p, "%s:%d:%d:%s", conf_rec->dir_conf->myproxy_server, conf_rec->dir_conf->myproxy_port, conf_rec->dir_conf->requested_lifetime, user); } return get_hash(p, input); } static char *get_user_filename(apr_pool_t *p, const authn_myproxy_conf_t *conf_rec, const char *user) { AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(conf_rec != NULL); AP_DEBUG_ASSERT(conf_rec->server_conf != NULL); AP_DEBUG_ASSERT(conf_rec->server_conf->cache_dir != NULL); AP_DEBUG_ASSERT(user != NULL); return apr_psprintf(p, "%s/%s-user", conf_rec->server_conf->cache_dir, get_user_hash_for_filename(p, conf_rec, user)); } static char *get_user_lock_filename(apr_pool_t *p, const authn_myproxy_conf_t *conf_rec, const char *user) { AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(conf_rec != NULL); AP_DEBUG_ASSERT(user != NULL); return apr_pstrcat(p, get_user_filename(p, conf_rec, user), "-lock", NULL); } static char *get_delegation_filename(apr_pool_t *p, const authn_myproxy_conf_t *conf_rec, const char *user) { AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(conf_rec != NULL); AP_DEBUG_ASSERT(conf_rec->server_conf != NULL); AP_DEBUG_ASSERT(conf_rec->server_conf->cache_dir != NULL); AP_DEBUG_ASSERT(user != NULL); /* Consider the following: * -- We store a delegation for Alice in "x" * -- We later reap Alice's record, but one of Alice's apps is * still running; it expects to find Alice's delegation in * the file with name "x" * -- We store a delegation for Bob in "x" * -- Alice's app reads "x" and finds Bob's delegation * * It is extremely unlikely that this scenario will happen, but * it's easy to mitigate: just include (a hash of) the username * (and server, etc.) in the filename. */ return apr_psprintf(p, "%s/%s-delegation", conf_rec->server_conf->cache_dir, get_user_hash_for_filename(p, conf_rec, user)); } static char *get_passphrase_hash(apr_pool_t *p, const char *user, const char *passphrase, const char *salt) { char *input; AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(user != NULL); AP_DEBUG_ASSERT(passphrase != NULL); AP_DEBUG_ASSERT(salt != NULL); input = apr_psprintf(p, "%s:%s:%s", salt, user, passphrase); return get_hash(p, input); } static char *get_salt(apr_pool_t *p) { unsigned char *salt; AP_DEBUG_ASSERT(p != NULL); salt = apr_palloc(p, AUTHN_MYPROXY_SALT_SIZE); if (apr_generate_random_bytes(salt, sizeof(salt)) != APR_SUCCESS) return NULL; return get_hex(p, salt, sizeof(salt)); } /* MUST have a lock before calling this -- has side effects */ static authn_myproxy_cache_t *prepare_cache_record(apr_pool_t *p, authn_myproxy_cache_t *old_cache_rec, authn_myproxy_cert_t *cert_rec, const authn_myproxy_conf_t *conf_rec, const char *user, const char *passphrase) { AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(cert_rec != NULL); AP_DEBUG_ASSERT(conf_rec != NULL); AP_DEBUG_ASSERT(user != NULL); AP_DEBUG_ASSERT(passphrase != NULL); /* If we fail, we must remove the temporary delegation */ if (old_cache_rec) { /* Update the delegation with an atomic rename. Keep the * same filename as used previously so existing apps can * get at the fresh delegation. */ if (apr_file_rename(cert_rec->file, old_cache_rec->cert->file, p) != APR_SUCCESS) { /* If it fails, destroy the temporary delegation */ apr_file_remove(cert_rec->file, p); return NULL; } /* reuse the old cache record; just update the cert record */ cert_rec->file = old_cache_rec->cert->file; old_cache_rec->cert = cert_rec; /* and we're done! */ return old_cache_rec; } else { /* No previous cache record existed, so our objective is to * build a new cache record around an existing certificate. */ char * final_cred_file; authn_myproxy_cache_t *cache_rec = apr_pcalloc(p, sizeof(*cache_rec)); cache_rec->user = apr_pstrdup(p, user); cache_rec->salt = get_salt(p); if (!cache_rec->salt) { /* This should never happen, but if it does, we should keep * the temporary delegation from hanging around... */ apr_file_remove(cert_rec->file, p); return NULL; } cache_rec->passphrase_hash = get_passphrase_hash(p, user, passphrase, cache_rec->salt); /* move credentials into final place */ final_cred_file = get_delegation_filename(p, conf_rec, user); if (apr_file_rename(cert_rec->file, final_cred_file, p) != APR_SUCCESS) return NULL; cert_rec->file = final_cred_file; /* simple pointer assignment, no need to deep-copy */ cache_rec->cert = cert_rec; cache_rec->myproxy_server = apr_pstrdup(p, conf_rec->dir_conf->myproxy_server); cache_rec->myproxy_port = conf_rec->dir_conf->myproxy_port; cache_rec->requested_lifetime = conf_rec->dir_conf->requested_lifetime; if (conf_rec->dir_conf->voname != NULL) { cache_rec->voname = apr_pstrdup(p, conf_rec->dir_conf->voname); } return cache_rec; } return NULL; /* never reached */ } static authn_myproxy_cert_t *get_cert_record(apr_pool_t *p, const char *filename) { globus_gsi_cred_handle_t proxy_cred = NULL; char *identity = NULL; time_t good_till; apr_time_t good_till_apr; authn_myproxy_cert_t *cert_rec = NULL; AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(filename != NULL); if (globus_gsi_cred_handle_init(&proxy_cred, NULL) != GLOBUS_SUCCESS) goto cleanup; if (globus_gsi_cred_read_proxy(proxy_cred, filename) != GLOBUS_SUCCESS) goto cleanup; if (globus_gsi_cred_get_goodtill(proxy_cred, &good_till) != GLOBUS_SUCCESS) goto cleanup; if (apr_time_ansi_put(&good_till_apr, good_till) != APR_SUCCESS) goto cleanup; /* globus_gsi_cred_handle.c revision 1.28.4.3 says in a comment: * "The identity_name is passed up and is freed by the caller - but * it must be freed with OPENSSL_free(), not free() and the caller * can't be expected to know that" */ if (globus_gsi_cred_get_identity_name(proxy_cred, &identity) != GLOBUS_SUCCESS) goto cleanup; /* Fill in our certificate structure */ cert_rec = apr_pcalloc(p, sizeof(*cert_rec)); cert_rec->file = apr_pstrdup(p, filename); cert_rec->identity = apr_pstrdup(p, identity); cert_rec->good_till = good_till_apr; cleanup: if (identity) OPENSSL_free(identity); if (proxy_cred) globus_gsi_cred_handle_destroy(proxy_cred); return cert_rec; } /* builds a cache record (and a certificate record) from a file */ /* from read_data_file() in myproxy_creds.c */ static authn_myproxy_cache_t *unserialize_cache_record(apr_pool_t *p, const char *filename) { authn_myproxy_cache_t *cache_rec; apr_file_t *f; int should_abort = 0; char *line; AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(filename != NULL); if (apr_file_open(&f, filename, APR_READ | APR_BUFFERED, APR_FPROT_UREAD | APR_FPROT_UWRITE, p) != APR_SUCCESS) return NULL; /* no such record */ cache_rec = apr_pcalloc(p, sizeof(*cache_rec)); cache_rec->cert = apr_pcalloc(p, sizeof(*(cache_rec->cert))); line = apr_pcalloc(p, MAX_STRING_LEN); while (1) { char *key, *value; int len; apr_status_t status; /* verified correct: no off-by-one error here */ status = apr_file_gets(line, MAX_STRING_LEN, f); if (status != APR_SUCCESS) { /* if failure was not due to EOF, abort */ if (status != APR_EOF) should_abort = 1; break; } if (line == NULL || *line == '\0') { should_abort = 1; break; } /* now, strlen(line) guaranteed >= 1 */ /* remove newline */ len = strlen(line); if (line[len - 1] == '\n') line[len - 1] = '\0'; key = line; value = ap_strchr(line, '='); if (value == NULL) { /* line did not contain '=' */ should_abort = 1; break; } *value = '\0'; value++; if (strcmp(key, "version") == 0 && strcmp(value, AUTHN_MYPROXY_VERSION) == 0) ; /* do nothing, but prevent falling through */ else if (strcmp(key, "user") == 0) cache_rec->user = apr_pstrdup(p, value); else if (strcmp(key, "salt") == 0) cache_rec->salt = apr_pstrdup(p, value); else if (strcmp(key, "passphrase_hash") == 0) cache_rec->passphrase_hash = apr_pstrdup(p, value); else if (strcmp(key, "myproxy_server") == 0) cache_rec->myproxy_server = apr_pstrdup(p, value); else if (strcmp(key, "myproxy_port") == 0) cache_rec->myproxy_port = atoi(value); else if (strcmp(key, "requested_lifetime") == 0) cache_rec->requested_lifetime = atoi(value); else if (strcmp(key, "cert_file") == 0) cache_rec->cert->file = apr_pstrdup(p, value); else if (strcmp(key, "cert_identity") == 0 && strlen(value) <= AUTHN_MYPROXY_MAX_DN_LEN) cache_rec->cert->identity = apr_pstrdup(p, value); else if (strcmp(key, "cert_good_till") == 0) cache_rec->cert->good_till = apr_date_parse_http(value); else if (strcmp(key, "voname") == 0) cache_rec->voname = apr_pstrdup(p, value); else { should_abort = 1; /* abort on unrecognized values */ break; } } apr_file_close(f); if (should_abort) return NULL; if (cache_rec->user == NULL || cache_rec->salt == NULL || cache_rec->passphrase_hash == NULL || cache_rec->myproxy_server == NULL || cache_rec->cert->identity == NULL || cache_rec->cert->file == NULL) return NULL; if (*(cache_rec->user) == '\0' || *(cache_rec->salt) == '\0' || *(cache_rec->passphrase_hash) == '\0' || *(cache_rec->myproxy_server) == '\0' || cache_rec->myproxy_port == 0 || cache_rec->requested_lifetime == 0 || *(cache_rec->cert->identity) == '\0' || *(cache_rec->cert->file) == '\0' || cache_rec->cert->good_till == APR_DATE_BAD) return NULL; return cache_rec; } static authn_myproxy_conf_t *build_conf_record(apr_pool_t *p, authn_myproxy_server_conf_t * server_conf_rec, authn_myproxy_dir_conf_t * dir_conf_rec) { authn_myproxy_conf_t *conf_rec; AP_DEBUG_ASSERT(p != NULL); conf_rec = apr_palloc(p, sizeof(*conf_rec)); conf_rec->dir_conf = dir_conf_rec; conf_rec->server_conf = server_conf_rec; return conf_rec; } /* tries to obtain a lock -- may fail */ static apr_file_t *get_lock(apr_pool_t *p, const authn_myproxy_conf_t *conf_rec, const char *user) { char *lockfile; apr_file_t *f = NULL; int i; AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(conf_rec != NULL); AP_DEBUG_ASSERT(user != NULL); lockfile = get_user_lock_filename(p, conf_rec, user); /* Don't want to block forever -- if a lock file was not * properly removed, a child could spin forever. */ /* We use APR_DELONCLOSE to delete the file when it is * closed; release_lock() just closes the file. This way, * APR will clean up the lockfile for us if something * goes wrong. */ for (i = 1; i <= AUTHN_MYPROXY_LOCK_TRIES; i++) if (apr_file_open(&f, lockfile, APR_CREATE | APR_EXCL | APR_DELONCLOSE | APR_FOPEN_READ | APR_FOPEN_WRITE, APR_FPROT_UREAD | APR_FPROT_UWRITE, p) == APR_SUCCESS) break; else apr_sleep(AUTHN_MYPROXY_LOCK_SLEEP); return f; } static apr_status_t release_lock(apr_file_t *f) { AP_DEBUG_ASSERT(f != NULL); return apr_file_close(f); } #if 0 /* the following version is not currently used * (now, we use APR's APR_DELONCLOSE flag to delete the file) */ static apr_status_t release_lock(apr_pool_t *p, const authn_myproxy_conf_t *conf_rec, const char *user) { char *lockfile; AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(conf_rec != NULL); AP_DEBUG_ASSERT(user != NULL); lockfile = get_user_lock_filename(p, conf_rec, user); return apr_file_remove(lockfile, p); } #endif /* finds the record for the given user; returns NULL if it does not exist */ static authn_myproxy_cache_t *get_cache_record(apr_pool_t *p, authn_myproxy_conf_t *conf_rec, const char *user, const char *passphrase) { char *filename, *in_hash; authn_myproxy_cache_t *cache_rec; AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(conf_rec != NULL); AP_DEBUG_ASSERT(user != NULL); AP_DEBUG_ASSERT(passphrase != NULL); AP_DEBUG_ASSERT(conf_rec->dir_conf != NULL); AP_DEBUG_ASSERT(conf_rec->dir_conf->myproxy_server != NULL); filename = get_user_filename(p, conf_rec, user); cache_rec = unserialize_cache_record(p, filename); if (cache_rec == NULL) /* couldn't open file, so assume no such user */ return NULL; /* return the record iff everything matches */ in_hash = get_passphrase_hash(p, user, passphrase, cache_rec->salt); if (strcmp(cache_rec->passphrase_hash, in_hash) == 0 && strcmp(cache_rec->user, user) == 0 && cache_rec->requested_lifetime == conf_rec->dir_conf->requested_lifetime && cache_rec->myproxy_port == conf_rec->dir_conf->myproxy_port && strcasecmp(cache_rec->myproxy_server, conf_rec->dir_conf->myproxy_server) == 0 && ((conf_rec->dir_conf->voname == NULL) || (strcasecmp(cache_rec->voname, conf_rec->dir_conf->voname) == 0))) return cache_rec; return NULL; } /* must own lock before calling this */ static apr_status_t put_cache_record(apr_pool_t *p, const authn_myproxy_conf_t *conf_rec, const authn_myproxy_cache_t *cache_rec) { char *filename; char *tempfile; apr_file_t *fp; char good_till_string[APR_RFC822_DATE_LEN]; apr_status_t status; AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(conf_rec != NULL); AP_DEBUG_ASSERT(cache_rec != NULL); /* find name of destination, temporary file; create temporary file */ filename = get_user_filename(p, conf_rec, cache_rec->user); tempfile = apr_pstrcat(p, filename, "-temp-XXXXXX", NULL); status = apr_file_mktemp(&fp, tempfile, APR_CREATE | APR_EXCL | APR_BUFFERED | APR_READ | APR_WRITE, p); if (status != APR_SUCCESS) return status; apr_file_printf(fp, "version=%s\n", AUTHN_MYPROXY_VERSION); apr_file_printf(fp, "user=%s\n", cache_rec->user); apr_file_printf(fp, "salt=%s\n", cache_rec->salt); apr_file_printf(fp, "passphrase_hash=%s\n", cache_rec->passphrase_hash); apr_file_printf(fp, "myproxy_server=%s\n", cache_rec->myproxy_server); apr_file_printf(fp, "myproxy_port=%d\n", cache_rec->myproxy_port); apr_file_printf(fp, "requested_lifetime=%d\n", cache_rec->requested_lifetime); apr_file_printf(fp, "cert_file=%s\n", cache_rec->cert->file); apr_file_printf(fp, "cert_identity=%s\n", cache_rec->cert->identity); apr_rfc822_date(good_till_string, cache_rec->cert->good_till); apr_file_printf(fp, "cert_good_till=%s\n", good_till_string); if (cache_rec->voname != NULL) { apr_file_printf(fp, "voname=%s\n", cache_rec->voname); } apr_file_flush(fp); apr_file_close(fp); /* commit */ status = apr_file_rename(tempfile, filename, p); if (status != APR_SUCCESS) return status; return APR_SUCCESS; } /* Apache handles configuration merging in a different way * than one would expect. A function creates the default * configuration, and then another function is called * each time a particular directive is set. That makes sense. * * However, directives in a new scope create a *new* default * config and then fill in that config. Then, the two configs * are merged. There's no simple way to just take the old * config and splice in the new values. * * mod_pubcookie says that the best way to deal with this is * to deal with this is to set bogus values initially. That * way, the merge function can determine which values have * been explicitly set and which have not. Then, when the * authentication handler is called, run one last sweep of * the config to set any unset values to sane defaults. * * This is only necessary for the directory config; the * server config is simple enough that we can write a decent * merge function. */ static void *create_dir_config(apr_pool_t *p, char *d) { authn_myproxy_dir_conf_t *dir_conf_rec = apr_palloc(p, sizeof(*dir_conf_rec)); dir_conf_rec->use_gridmap = -1; dir_conf_rec->set_logname = -1; dir_conf_rec->requested_lifetime = 0; dir_conf_rec->min_lifetime = 0; dir_conf_rec->myproxy_server = NULL; dir_conf_rec->myproxy_port = 0; dir_conf_rec->voname = NULL; dir_conf_rec->voms_server = NULL; dir_conf_rec->voms_port = 0; dir_conf_rec->voms_server_host_dn = NULL; return (void *)dir_conf_rec; } static void *create_server_config(apr_pool_t *p, server_rec *s) { authn_myproxy_server_conf_t *server_conf_rec = apr_palloc(p, sizeof(*server_conf_rec)); server_conf_rec->cache_dir = NULL; server_conf_rec->module_failed = 0; return (void *)server_conf_rec; } static void *merge_server_config(apr_pool_t *p, void *BASE, void *ADD) { authn_myproxy_server_conf_t *base = (authn_myproxy_server_conf_t *)BASE; authn_myproxy_server_conf_t *add = (authn_myproxy_server_conf_t *)ADD; authn_myproxy_server_conf_t *merged = apr_pcalloc(p, sizeof(*merged)); if (add->cache_dir == NULL && base->cache_dir == NULL) merged->cache_dir = NULL; else if (add->cache_dir != NULL) merged->cache_dir = apr_pstrdup(p, add->cache_dir); else if (base->cache_dir != NULL) merged->cache_dir = apr_pstrdup(p, base->cache_dir); if (base->module_failed || add->module_failed) merged->module_failed = 1; return (void *)merged; } /* see create_dir_config */ static void *merge_dir_config(apr_pool_t *p, void *BASE, void *ADD) { authn_myproxy_dir_conf_t *base = (authn_myproxy_dir_conf_t *)BASE; authn_myproxy_dir_conf_t *add = (authn_myproxy_dir_conf_t *)ADD; authn_myproxy_dir_conf_t *merged = apr_palloc(p, sizeof(*merged)); merged->use_gridmap = (add->use_gridmap == -1) ? base->use_gridmap : add->use_gridmap; merged->set_logname = (add->set_logname == -1) ? base->set_logname : add->set_logname; merged->requested_lifetime = (add->requested_lifetime == 0) ? base->requested_lifetime : add->requested_lifetime; merged->min_lifetime = (add->min_lifetime == 0) ? base->min_lifetime : add->min_lifetime; merged->myproxy_port = (add->myproxy_port == 0) ? base->myproxy_port : add->myproxy_port; if (add->myproxy_server == NULL && base->myproxy_server == NULL) merged->myproxy_server = NULL; else if (add->myproxy_server != NULL) merged->myproxy_server = apr_pstrdup(p, add->myproxy_server); else if (base->myproxy_server != NULL) merged->myproxy_server = apr_pstrdup(p, base->myproxy_server); if (add->voname == NULL && base->voname == NULL) merged->voname = NULL; else if (add->voname != NULL) merged->voname = apr_pstrdup(p, add->voname); else if (base->voname != NULL) merged->voname = apr_pstrdup(p, base->voname); if (add->voms_server == NULL && base->voms_server == NULL) merged->voms_server = NULL; else if (add->voms_server != NULL) merged->voms_server = apr_pstrdup(p, add->voms_server); else if (base->voms_server != NULL) merged->voms_server = apr_pstrdup(p, base->voms_server); merged->voms_port = (add->voms_port == 0) ? base->voms_port : add->voms_port; if (add->voms_server_host_dn == NULL && base->voms_server_host_dn == NULL) merged->voms_server_host_dn = NULL; else if (add->voms_server_host_dn != NULL) merged->voms_server_host_dn = apr_pstrdup(p, add->voms_server_host_dn); else if (base->voms_server_host_dn != NULL) merged->voms_server_host_dn = apr_pstrdup(p, base->voms_server_host_dn); return (void *)merged; } /* see create_dir_config */ static void build_dir_conf(authn_myproxy_dir_conf_t *dir_conf_rec) { if (dir_conf_rec->use_gridmap == -1) dir_conf_rec->use_gridmap = AUTHN_MYPROXY_DEFAULT_USE_GRIDMAP; if (dir_conf_rec->set_logname == -1) dir_conf_rec->set_logname = AUTHN_MYPROXY_DEFAULT_SET_LOGNAME; if (dir_conf_rec->requested_lifetime == 0) dir_conf_rec->requested_lifetime = AUTHN_MYPROXY_DEFAULT_REQUESTED_LIFETIME; if (dir_conf_rec->min_lifetime == 0) dir_conf_rec->min_lifetime = AUTHN_MYPROXY_DEFAULT_MIN_LIFETIME; /* no default for MyProxy server */ if (dir_conf_rec->myproxy_port == 0) dir_conf_rec->myproxy_port = AUTHN_MYPROXY_DEFAULT_MYPROXY_PORT; if (dir_conf_rec->voms_port == 0) dir_conf_rec->voms_port = AUTHN_MYPROXY_DEFAULT_VOMS_PORT; } static apr_status_t my_myproxy_free(void *data) { authn_myproxy_conn_t *conn_rec = (authn_myproxy_conn_t *)data; if (conn_rec) myproxy_free(conn_rec->socket_attrs, conn_rec->request, conn_rec->response); verror_clear(); return APR_SUCCESS; } /* Attempts to create a connection structure. Wraps MyProxy's memory * allocation so that we can use APR to manage the connection. Apache * will call my_myproxy_free() when the pool is cleaned; that function * will call myproxy_free(). */ static authn_myproxy_conn_t *my_myproxy_init(apr_pool_t *p, const authn_myproxy_conf_t *conf_rec, const char *user, const char *passphrase) { authn_myproxy_conn_t *conn_rec = NULL; AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(conf_rec != NULL); AP_DEBUG_ASSERT(user != NULL); AP_DEBUG_ASSERT(passphrase != NULL); conn_rec = apr_pcalloc(p, sizeof(*conn_rec)); apr_pool_cleanup_register(p, conn_rec, my_myproxy_free, apr_pool_cleanup_null); conn_rec->socket_attrs = malloc(sizeof(*(conn_rec->socket_attrs))); if (conn_rec->socket_attrs == NULL) return NULL; memset(conn_rec->socket_attrs, '\0', sizeof(*(conn_rec->socket_attrs))); conn_rec->request = malloc(sizeof(*(conn_rec->request))); if (conn_rec->request == NULL) return NULL; memset(conn_rec->request, '\0', sizeof(*(conn_rec->request))); conn_rec->response = malloc(sizeof(*(conn_rec->response))); if (conn_rec->response == NULL) return NULL; memset(conn_rec->response, '\0', sizeof(*(conn_rec->response))); conn_rec->request->username = strdup(user); if (conn_rec->request->username == NULL) return NULL; conn_rec->socket_attrs->pshost = strdup(conf_rec->dir_conf->myproxy_server); if (conn_rec->socket_attrs->pshost == NULL) return NULL; conn_rec->socket_attrs->psport = conf_rec->dir_conf->myproxy_port; apr_cpystrn(conn_rec->request->passphrase, passphrase, MAX_PASS_LEN); return conn_rec; } static authn_myproxy_cert_t *my_get_delegation(apr_pool_t *p, request_rec *r, const authn_myproxy_conf_t *conf_rec, const char *user, const char *passphrase) { authn_myproxy_conn_t *conn_rec = NULL; authn_myproxy_cert_t *cert_rec = NULL; char *datafile = NULL; apr_file_t *datafp = NULL; char *tempfile = NULL; apr_file_t *tempfp = NULL; int have_datafile = 0, have_tempfile = 0, should_remove_datafile = 0; AP_DEBUG_ASSERT(p != NULL); AP_DEBUG_ASSERT(r != NULL); AP_DEBUG_ASSERT(conf_rec != NULL); AP_DEBUG_ASSERT(user != NULL); AP_DEBUG_ASSERT(passphrase != NULL); conn_rec = my_myproxy_init(p, conf_rec, user, passphrase); if (conn_rec == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Could not allocate MyProxy connection record"); goto cleanup; } myproxy_set_delegation_defaults(conn_rec->socket_attrs, conn_rec->request); if (conf_rec->dir_conf->voname != NULL) { if (myproxy_request_add_voname(conn_rec->request, conf_rec->dir_conf->voname) < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "myproxy_request_add_voname error: %s", verror_get_string()); goto cleanup; } if ((conf_rec->dir_conf->voms_server != NULL) && (conf_rec->dir_conf->voms_server_host_dn != NULL)) { char *tmp = NULL; tmp = apr_psprintf(p, "\"%s\" \"%s\" \"%d\" \"%s\" \"%s\"", conf_rec->dir_conf->voname, conf_rec->dir_conf->voms_server, conf_rec->dir_conf->voms_port, conf_rec->dir_conf->voms_server_host_dn, conf_rec->dir_conf->voname); if (tmp == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Could not allocate a vomses line"); goto cleanup; } if (myproxy_request_add_vomses(conn_rec->request, tmp) < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "myproxy_request_add_vomses error: %s", verror_get_string()); goto cleanup; } } } conn_rec->request->proxy_lifetime = conf_rec->dir_conf->requested_lifetime * 60; if (myproxy_init_client(conn_rec->socket_attrs) < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "myproxy_init_client error: %s", verror_get_string()); goto cleanup; } /* myproxy_get_delegation will **unlink** the destination file * and then recreate it! This is not good, because it prevents * us from using mkstemp() safely. We could create a file "x" * securely and call myproxy_get_delegation, which could then * unlink "x". Before it recreates the file, another mkstemp() * call could assign "x" to another request. Our delegation * attempt will fail if this happens. * * We get around it this way: * -- mkstemp() to create a file "x" (we call "x" the data file) * -- myproxy_get_delegation() with output file "x-temp" * -- if successful, rename "x-temp" to "x" */ datafile = apr_pstrcat(p, get_delegation_filename(p, conf_rec, user), "-temp-XXXXXX", NULL); if (apr_file_mktemp(&datafp, datafile, APR_CREATE | APR_READ | APR_WRITE | APR_EXCL, p) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Could not create temporary (data) file %s for " "MyProxy delegation", datafile); goto cleanup; } have_datafile = 1; /* remember to clean it up later */ should_remove_datafile = 1; apr_file_close(datafp); /* don't care about this handle */ datafp = NULL; tempfile = apr_pstrcat(p, datafile, "-2", NULL); if (apr_file_open(&tempfp, tempfile, APR_CREATE | APR_READ | APR_WRITE | APR_EXCL, APR_FPROT_UREAD | APR_FPROT_UWRITE, p) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Could not create temporary (data) file for MyProxy delegation"); goto cleanup; } have_tempfile = 1; if (myproxy_get_delegation(conn_rec->socket_attrs, conn_rec->request, NULL, conn_rec->response, tempfile) != 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Could not acquire MyProxy delegation for %s: %s", user, verror_get_string()); goto cleanup; } /* find information about the delegation we just received */ cert_rec = get_cert_record(p, tempfile); if (cert_rec == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Could not retrieve certificate information (%s)", datafile); goto cleanup; } /* did we get a DN? */ if (cert_rec->identity == NULL || *(cert_rec->identity) == '\0' || strlen(cert_rec->identity) > AUTHN_MYPROXY_MAX_DN_LEN) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Could not get identity DN (%s)", datafile); cert_rec = NULL; goto cleanup; } /* did we get an acceptable lifetime? */ if (!is_cert_current(conf_rec, cert_rec)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Received a certificate with unacceptable lifetime, rejecting"); cert_rec = NULL; goto cleanup; } apr_file_close(tempfp); tempfp = NULL; if (apr_file_rename(tempfile, datafile, p) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Could not rename temporary file for MyProxy delegation"); goto cleanup; cert_rec = NULL; } cert_rec->file = datafile; should_remove_datafile = 0; /* has our data now */ have_tempfile = 0; /* done with tempfile */ cleanup: if (have_tempfile) { if (tempfp) apr_file_close(tempfp); apr_file_remove(tempfile, p); } if (have_datafile) { if (datafp) apr_file_close(datafp); if (should_remove_datafile) apr_file_remove(datafile, p); } /* APR will clean up MyProxy memory */ return cert_rec; } static int sanity_check_dir(apr_pool_t *p, const char *dir) { apr_finfo_t finfo; apr_uid_t my_uid; apr_gid_t my_gid; if (dir == NULL || *dir == '\0') return 0; if (strcmp(dir, "/tmp") == 0) return 0; if (apr_stat(&finfo, dir, APR_FINFO_TYPE | APR_FINFO_OWNER, p) != APR_SUCCESS || finfo.filetype != APR_DIR) return 0; /* No need to check group ID right now. To readd it, just * use apr_gid_compare(my_gid, finfo.group) != APR_SUCCESS) */ if (apr_uid_current(&my_uid, &my_gid, p) != APR_SUCCESS || apr_uid_compare(my_uid, finfo.user) != APR_SUCCESS) return 0; if (finfo.protection != (APR_FPROT_UREAD | APR_FPROT_UWRITE | APR_FPROT_UEXECUTE)) return 0; return 1; } static int sanity_check_config(request_rec *r, const authn_myproxy_conf_t *conf_rec) { char *gridmap_filename; apr_finfo_t finfo; AP_DEBUG_ASSERT(r != NULL); AP_DEBUG_ASSERT(conf_rec != NULL); AP_DEBUG_ASSERT(conf_rec->server_conf != NULL); AP_DEBUG_ASSERT(conf_rec->dir_conf != NULL); if (!sanity_check_dir(r->pool, conf_rec->server_conf->cache_dir)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Can't use temporary directory %s with mod_authn_myproxy", conf_rec->server_conf->cache_dir); return 0; } if (conf_rec->dir_conf->myproxy_server == NULL || *(conf_rec->dir_conf->myproxy_server) == '\0' || conf_rec->dir_conf->myproxy_port == 0 || ap_strchr(conf_rec->dir_conf->myproxy_server, '\r') != NULL || ap_strchr(conf_rec->dir_conf->myproxy_server, '\n') != NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "MyProxy server not set"); return 0; } if (conf_rec->dir_conf->requested_lifetime <= 0 || conf_rec->dir_conf->min_lifetime <= 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "MyProxy delegation lifetimes not set"); return 0; } if (conf_rec->dir_conf->requested_lifetime < 5) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "As of MyProxy 3.5, the minimum delegation " "lifetime is five minutes"); return 0; } if (conf_rec->dir_conf->requested_lifetime <= conf_rec->dir_conf->min_lifetime) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "MyProxy requested lifetime <= minimum lifetime"); return 0; } if (conf_rec->dir_conf->use_gridmap) { /* must free() this */ GLOBUS_GSI_SYSCONFIG_GET_GRIDMAP_FILENAME(&gridmap_filename); if (gridmap_filename == NULL || apr_stat(&finfo, gridmap_filename, 0, r->pool) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "gridmap (%s) does not exist -- perhaps you want to set " "the GRIDMAP environment variable?", gridmap_filename); free(gridmap_filename); return 0; } free(gridmap_filename); } return 1; } static authn_status authn_myproxy_authenticate(request_rec *r, const char *user, const char *passphrase) { authn_myproxy_conf_t *conf_rec = NULL; authn_myproxy_cache_t *cache_rec = NULL; authn_myproxy_dir_conf_t *dir_conf_rec = ap_get_module_config(r->per_dir_config, &authn_myproxy_module); authn_myproxy_server_conf_t *server_conf_rec = ap_get_module_config(r->server->module_config, &authn_myproxy_module); AP_DEBUG_ASSERT(r != NULL); AP_DEBUG_ASSERT(dir_conf_rec != NULL); AP_DEBUG_ASSERT(server_conf_rec != NULL); build_dir_conf(dir_conf_rec); conf_rec = build_conf_record(r->pool, server_conf_rec, dir_conf_rec); if (conf_rec->server_conf->module_failed || !sanity_check_config(r, conf_rec)) return AUTH_GENERAL_ERROR; /* Reject blatantly bogus input */ if (user == NULL || passphrase == NULL || strlen(user) == 0 || strlen(passphrase) == 0 || ap_strchr(user, '\r') || ap_strchr(passphrase, '\r') || ap_strchr(user, '\n') || ap_strchr(passphrase, '\n')) return AUTH_DENIED; /* Right now, we intentionally don't lock here. We take advantage * of POSIX's atomic rename to ensure that readers do not have to * stop for writers. */ /* If a record for the user exists, but the passphrase does not * match, get_cache_record() will return NULL and we'll attempt to * get a delegation for the user with their most recent passphrase. * * This is appropriate and correct behavior for two reasons: * -- the user may have changed his MyProxy passphrase * -- if we reject the request without contacting MyProxy, we * are leaking information. An attacker could attempt to * login using a victim's username and a bogus password. * By observing how long it takes for the request to be * rejected, he can determine if the victim has used the * service recently. */ cache_rec = get_cache_record(r->pool, conf_rec, user, passphrase); if (cache_rec == NULL || !is_cert_current(conf_rec, cache_rec->cert)) { apr_file_t *f; authn_myproxy_cert_t *cert_rec; f = get_lock(r->pool, conf_rec, user); if (f == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "MyProxy: could not acquire lock for user %s", user); return AUTH_GENERAL_ERROR; } /* The cache wasn't locked as we were doing our first read. * We need to reread it to see if anything has changed. (This * effectively serializes outgoing MyProxy requests on a per-user * basis.) Iff it's still not valid, continue with our request. */ cache_rec = get_cache_record(r->pool, conf_rec, user, passphrase); if (cache_rec == NULL || !is_cert_current(conf_rec, cache_rec->cert)) { cert_rec = my_get_delegation(r->pool, r, conf_rec, user, passphrase); if (cert_rec == NULL) { release_lock(f); return AUTH_DENIED; } /* Now, we have a valid certificate. Construct a cache record * around it, based on the current cache record (if any). This * will rename the credential file if appropriate. */ cache_rec = prepare_cache_record(r->pool, cache_rec, cert_rec, conf_rec, user, passphrase); put_cache_record(r->pool, conf_rec, cache_rec); } release_lock(f); } /* We intentionally do not cache the gridmap result */ if (conf_rec->dir_conf->use_gridmap && !check_gridmap(cache_rec)) return AUTH_USER_NOT_FOUND; /* Finally, set up the environment */ apr_table_set(r->subprocess_env, "X509_USER_DN", cache_rec->cert->identity); apr_table_set(r->subprocess_env, "X509_USER_PROXY", cache_rec->cert->file); apr_table_set(r->subprocess_env, "MYPROXY_SERVER", conf_rec->dir_conf->myproxy_server); apr_table_set(r->subprocess_env, "MYPROXY_SERVER_PORT", apr_itoa(r->pool, conf_rec->dir_conf->myproxy_port)); if (conf_rec->dir_conf->set_logname) apr_table_set(r->subprocess_env, "LOGNAME", user); return AUTH_GRANTED; } static const char *set_cache_dir(cmd_parms *cmd, void *dummy, const char *dir) { authn_myproxy_server_conf_t *server_conf_rec = (authn_myproxy_server_conf_t *)ap_get_module_config( cmd->server->module_config, &authn_myproxy_module); if (dir == NULL || *dir == '\0') return "MyProxy: temporary directory not set"; /* We should defer directory sanity checking -- * we may have to drop privileges to get a meaningful result. */ server_conf_rec->cache_dir = ap_server_root_relative(cmd->pool, dir); return NULL; } static int initialize_module(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { void *flag; const char *userdata_key = "authn_myproxy_init"; int threaded = 1; /* DSOs are loaded twice; make sure we only initialize once */ apr_pool_userdata_get(&flag, userdata_key, s->process->pool); if (!flag) { apr_pool_userdata_set((const void *)1, userdata_key, apr_pool_cleanup_null, s->process->pool); return OK; } if (myproxy_check_version() != 0) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "MyProxy library version mismatch: expected %s, found %s", MYPROXY_VERSION_DATE, myproxy_version(0, 0, 0)); return HTTP_INTERNAL_SERVER_ERROR; } ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded); if (threaded) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "This MPM is threaded -- the MyProxy library is not thread-safe"); return HTTP_INTERNAL_SERVER_ERROR; } return OK; } static apr_status_t deinitialize_module_child(void *s) { globus_module_deactivate_all(); return APR_SUCCESS; } /* Globus modules must be activated before use. Some modules run into problems * if they are not initialized correctly. For an example, see this advisory: * http://www.globus.org/mail_archive/security-announce/Archive/msg00003.html * * The activation specification does not state whether activation can be * carried across a fork(). I suspect this depends on the particular module. * SQLite database handles, for example, cannot be carried across a fork(). * * So, we will play it safe and reinitialize with each new process. * * ("child_init" events don't seem to run on new threads; see mpm_netware.c) */ static void initialize_module_child(apr_pool_t *p, server_rec *s) { authn_myproxy_server_conf_t *server_conf_rec = ap_get_module_config(s->module_config, &authn_myproxy_module); if (globus_module_activate(GLOBUS_GSI_SYSCONFIG_MODULE) != GLOBUS_SUCCESS || globus_module_activate(GLOBUS_GSI_CREDENTIAL_MODULE) != GLOBUS_SUCCESS || globus_module_activate(GLOBUS_GSI_GSS_ASSIST_MODULE) != GLOBUS_SUCCESS || globus_module_activate(GLOBUS_OPENSSL_MODULE) != GLOBUS_SUCCESS) { /* fail closed */ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "MyProxy: a Globus module did not properly activate -- " "authentication will fail"); server_conf_rec->module_failed = 1; } apr_pool_cleanup_register(p, s, deinitialize_module_child, deinitialize_module_child); } static const authn_provider authn_myproxy_provider = { &authn_myproxy_authenticate, NULL }; static void register_hooks(apr_pool_t *p) { ap_register_provider(p, AUTHN_PROVIDER_GROUP, "myproxy", "0", &authn_myproxy_provider); ap_hook_post_config(initialize_module, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_child_init(initialize_module_child, NULL, NULL, APR_HOOK_MIDDLE); } static const command_rec authn_myproxy_cmds[] = { /* per-directory configuration */ AP_INIT_FLAG("MyProxyCheckGridmap", ap_set_flag_slot, (void*)APR_OFFSETOF(authn_myproxy_dir_conf_t, use_gridmap), OR_AUTHCFG, "Check remote username and DN against gridmap"), AP_INIT_FLAG("MyProxySetLogname", ap_set_flag_slot, (void*)APR_OFFSETOF(authn_myproxy_dir_conf_t, set_logname), OR_AUTHCFG, "Set the LOGNAME environment variable"), AP_INIT_TAKE1("MyProxyMinLifetime", ap_set_int_slot, (void*)APR_OFFSETOF(authn_myproxy_dir_conf_t, min_lifetime), OR_AUTHCFG, "Minimum lifetime required (in minutes)"), AP_INIT_TAKE1("MyProxyServer", ap_set_string_slot, (void*)APR_OFFSETOF(authn_myproxy_dir_conf_t, myproxy_server), OR_AUTHCFG, "Address of MyProxy server"), AP_INIT_TAKE1("MyProxyPort", ap_set_int_slot, (void*)APR_OFFSETOF(authn_myproxy_dir_conf_t, myproxy_port), OR_AUTHCFG, "Port for MyProxy server"), AP_INIT_TAKE1("MyProxyRequestedLifetime", ap_set_int_slot, (void *)APR_OFFSETOF(authn_myproxy_dir_conf_t, requested_lifetime), OR_AUTHCFG, "Requested lifetime for delegation (in minutes)"), AP_INIT_TAKE1("VOName", ap_set_string_slot, (void*)APR_OFFSETOF(authn_myproxy_dir_conf_t, voname), OR_AUTHCFG, "VO Name"), AP_INIT_TAKE1("VomsServer", ap_set_string_slot, (void*)APR_OFFSETOF(authn_myproxy_dir_conf_t, voms_server), OR_AUTHCFG, "VOMS Server Hostname"), AP_INIT_TAKE1("VomsServerPort", ap_set_int_slot, (void*)APR_OFFSETOF(authn_myproxy_dir_conf_t, voms_port), OR_AUTHCFG, "Port for VOMS server"), AP_INIT_TAKE1("VomsServerHostDN", ap_set_string_slot, (void*)APR_OFFSETOF(authn_myproxy_dir_conf_t, voms_server_host_dn), OR_AUTHCFG, "VOMS Server Host Distinguished Name (DN)"), /* per-server configuration */ /* Only the administrator should be able to force the web server * to create files in an arbitrary directory. Don't let users * override this in an .htaccess file. */ AP_INIT_TAKE1("MyProxyCacheDir", set_cache_dir, NULL, RSRC_CONF, "Directory for cache files"), { NULL } }; module AP_MODULE_DECLARE_DATA authn_myproxy_module = { STANDARD20_MODULE_STUFF, create_dir_config, merge_dir_config, create_server_config, merge_server_config, authn_myproxy_cmds, register_hooks };