| /* Copyright 2002-2005 The Apache Software Foundation or its licensors, as |
| * applicable. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * Security options etc. |
| * |
| * Module derived from code originally written by Rob McCool |
| * |
| */ |
| |
| #include "apr_strings.h" |
| #include "apr_network_io.h" |
| #include "apr_md5.h" |
| |
| #define APR_WANT_STRFUNC |
| #define APR_WANT_BYTEFUNC |
| #include "apr_want.h" |
| |
| #define CORE_PRIVATE |
| #include "ap_config.h" |
| #include "httpd.h" |
| #include "http_config.h" |
| #include "http_core.h" |
| #include "http_log.h" |
| #include "http_request.h" |
| #include "http_protocol.h" |
| #include "ap_provider.h" |
| |
| #include "mod_auth.h" |
| |
| #if APR_HAVE_NETINET_IN_H |
| #include <netinet/in.h> |
| #endif |
| |
| /* TODO List |
| |
| X- Convert all of the authz modules to providers |
| X- Remove the ap_requires field from the core_dir_config structure |
| X- Remove the ap_requires field from authz_dir_conf |
| X- Remove the function ap_requires() and authz_ap_requires() |
| since their functionality is no longer supported |
| or necessary in the refactoring |
| X- Remove the calls to ap_some_auth_required() in the |
| core request handling to allow the hooks to be called |
| in all cases. Is this function even necessary |
| anymore? |
| - Determine of merge_authz_dir_config is even |
| necessary and remove if not |
| X- Split the authz type from the arguments when the |
| authz provider is registered and store the type |
| in ->provider_name and the arguments in ->requirement |
| X- Move the check for METHOD_MASK out of the authz |
| providers and into the provider vector |
| X- Change the status code to AUTHZ_DENIED, AUTHZ_GRANTED |
| and AUTHZ_GENERAL_ERROR |
| - Determine if setting the AUTHZ_PROVIDER_NAME_NOTE note |
| is even necessary. This was used in authn to support |
| authn_alias. Is there a need for an authz_alias? |
| X- Remove the Satisfy directive functionality and replace it with the |
| <SatisfyAll>, <SatisfyOne> directives |
| X- Remove the Satisfy directive |
| X- Implement the <SatisfyAll> <SatisfyOne> block directives |
| to handle the 'and' and 'or' logic for authorization. |
| X- Remove the AuthzXXXAuthoritative directives from all of |
| the authz providers |
| X- Implement the Reject directive that will deny authorization |
| if the argument is true |
| X- Fold the Reject directive into the <SatisfyAll> <SatisfyOne> |
| logic |
| X- Reimplement the host based authorization 'allow', 'deny' |
| and 'order' as authz providers |
| X- Remove the 'allow', 'deny' and 'order' directives |
| - Merge mod_authn_alias into mod_authn_core |
| X- Remove all of the references to the authzxxxAuthoritative |
| directives from the documentation |
| X- Remove the Satisfy directive from the documentation |
| */ |
| |
| typedef struct provider_alias_rec { |
| char *provider_name; |
| char *provider_alias; |
| char *provider_args; |
| ap_conf_vector_t *sec_auth; |
| const authz_provider *provider; |
| } provider_alias_rec; |
| |
| typedef struct { |
| authz_provider_list *providers; |
| authz_request_state req_state; |
| int req_state_level; |
| } authz_core_dir_conf; |
| |
| typedef struct authz_core_srv_conf { |
| apr_hash_t *alias_rec; |
| } authz_core_srv_conf; |
| |
| |
| module AP_MODULE_DECLARE_DATA authz_core_module; |
| |
| static void *create_authz_core_dir_config(apr_pool_t *p, char *dummy) |
| { |
| authz_core_dir_conf *conf = |
| (authz_core_dir_conf *)apr_pcalloc(p, sizeof(authz_core_dir_conf)); |
| |
| conf->req_state = AUTHZ_REQSTATE_ONE; |
| conf->req_state_level = 0; |
| return (void *)conf; |
| } |
| |
| #if 0 |
| static void *merge_authz_core_dir_config(apr_pool_t *a, void *basev, void *newv) |
| { |
| authz_core_dir_conf *base = (authz_core_dir_conf *)basev; |
| authz_core_dir_conf *new = (authz_core_dir_conf *)newv; |
| authz_core_dir_conf *conf; |
| |
| /* Create this conf by duplicating the base, replacing elements |
| * (or creating copies for merging) where new-> values exist. |
| */ |
| conf = (authz_core_dir_conf *)apr_palloc(a, sizeof(authz_core_dir_conf)); |
| memcpy(conf, base, sizeof(authz_core_dir_conf)); |
| |
| return (void*)conf; |
| } |
| #endif |
| |
| static void *create_authz_core_svr_config(apr_pool_t *p, server_rec *s) |
| { |
| |
| authz_core_srv_conf *authcfg; |
| |
| authcfg = (authz_core_srv_conf *) apr_pcalloc(p, sizeof(authz_core_srv_conf)); |
| authcfg->alias_rec = apr_hash_make(p); |
| |
| return (void *) authcfg; |
| } |
| |
| static const char *add_authz_provider(cmd_parms *cmd, void *config, |
| const char *arg) |
| { |
| authz_core_dir_conf *conf = (authz_core_dir_conf*)config; |
| authz_provider_list *newp; |
| const char *t, *w; |
| |
| newp = apr_pcalloc(cmd->pool, sizeof(authz_provider_list)); |
| |
| t = arg; |
| w = ap_getword_white(cmd->pool, &t); |
| |
| if (w) |
| newp->provider_name = apr_pstrdup(cmd->pool, w); |
| if (t) |
| newp->requirement = apr_pstrdup(cmd->pool, t); |
| newp->method_mask = cmd->limited; |
| |
| /* lookup and cache the actual provider now */ |
| newp->provider = ap_lookup_provider(AUTHZ_PROVIDER_GROUP, |
| newp->provider_name, "0"); |
| newp->req_state = conf->req_state; |
| newp->req_state_level = conf->req_state_level; |
| newp->is_reject = (int)cmd->info; |
| |
| /* by the time the config file is used, the provider should be loaded |
| * and registered with us. |
| */ |
| if (newp->provider == NULL) { |
| return apr_psprintf(cmd->pool, |
| "Unknown Authz provider: %s", |
| newp->provider_name); |
| } |
| |
| /* if the provider doesn't provide the appropriate function, reject it */ |
| if (!newp->provider->check_authorization) { |
| return apr_psprintf(cmd->pool, |
| "The '%s' Authz provider is not supported by any " |
| "of the loaded authorization modules ", |
| newp->provider_name); |
| } |
| |
| /* Add it to the list now. */ |
| if (!conf->providers) { |
| conf->providers = newp; |
| } |
| else { |
| authz_provider_list *last = conf->providers; |
| int level = conf->req_state_level; |
| |
| /* if the level is 0 then take care of the implicit 'or' |
| operation at this level. */ |
| if (level == 0) { |
| /* Just run through the Require_one list and add the |
| node */ |
| while (last->one_next) { |
| last = last->one_next; |
| } |
| last->one_next = newp; |
| } |
| else { |
| /* Traverse the list to find the last entry.Each level |
| indicates a transition in the logic. */ |
| for (;level;level--) { |
| /* if we are in a Require_all block then run through |
| all of the Require_all nodes to the end of the list */ |
| if (last->req_state == AUTHZ_REQSTATE_ALL) { |
| while (last->all_next) { |
| last = last->all_next; |
| } |
| /* If the end of the list contains a node state |
| change then run through all of the Require_one |
| nodes to the end of that list */ |
| if (level >= last->req_state_level) { |
| while (last->one_next) { |
| last = last->one_next; |
| } |
| } |
| continue; |
| } |
| /* if we are in a Require_one block then run through |
| all of the Require_one nodes to the end of the list */ |
| if (last->req_state == AUTHZ_REQSTATE_ONE) { |
| while (last->one_next) { |
| last = last->one_next; |
| } |
| /* If the end of the list contains a node state |
| change then run through all of the Require_all |
| nodes to the end of that list */ |
| if (level >= last->req_state_level) { |
| while (last->all_next) { |
| last = last->all_next; |
| } |
| } |
| continue; |
| } |
| } |
| |
| /* The current state flag indicates which way the transition should |
| go. If ALL then take the all_next path, otherwise one_next */ |
| if (last->req_state == AUTHZ_REQSTATE_ALL) { |
| /* If we already have an all_next node, then |
| we must have dropped back a level so assign |
| the node to one_next */ |
| if (!last->all_next) { |
| last->all_next = newp; |
| } |
| else |
| last->one_next = newp; |
| } |
| else { |
| /* If we already have a one_next node, then |
| we must have dropped back a level so assign |
| the node to all_next */ |
| if (!last->one_next) { |
| last->one_next = newp; |
| } |
| else |
| last->all_next = newp; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* This is a fake authz provider that really merges various authz alias |
| configurations and then envokes them. */ |
| static authz_status authz_alias_check_authorization(request_rec *r, |
| const char *require_args) |
| { |
| /* Look up the provider alias in the alias list */ |
| /* Get the the dir_config and call ap_Merge_per_dir_configs() */ |
| /* Call the real provider->check_authorization() function */ |
| /* return the result of the above function call */ |
| |
| const char *provider_name = apr_table_get(r->notes, AUTHZ_PROVIDER_NAME_NOTE); |
| authz_status ret = AUTHZ_DENIED; |
| authz_core_srv_conf *authcfg = |
| (authz_core_srv_conf *)ap_get_module_config(r->server->module_config, |
| &authz_core_module); |
| |
| if (provider_name) { |
| provider_alias_rec *prvdraliasrec = apr_hash_get(authcfg->alias_rec, |
| provider_name, APR_HASH_KEY_STRING); |
| ap_conf_vector_t *orig_dir_config = r->per_dir_config; |
| |
| /* If we found the alias provider in the list, then merge the directory |
| configurations and call the real provider */ |
| if (prvdraliasrec) { |
| r->per_dir_config = ap_merge_per_dir_configs(r->pool, orig_dir_config, |
| prvdraliasrec->sec_auth); |
| ret = prvdraliasrec->provider->check_authorization(r, prvdraliasrec->provider_args); |
| r->per_dir_config = orig_dir_config; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static const authz_provider authz_alias_provider = |
| { |
| &authz_alias_check_authorization, |
| }; |
| |
| static const char *authz_require_alias_section(cmd_parms *cmd, void *mconfig, const char *arg) |
| { |
| int old_overrides = cmd->override; |
| const char *endp = ap_strrchr_c(arg, '>'); |
| const char *args; |
| char *provider_alias; |
| char *provider_name; |
| char *provider_args; |
| const char *errmsg; |
| ap_conf_vector_t *new_authz_config = ap_create_per_dir_config(cmd->pool); |
| authz_core_srv_conf *authcfg = |
| (authz_core_srv_conf *)ap_get_module_config(cmd->server->module_config, |
| &authz_core_module); |
| |
| const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); |
| if (err != NULL) { |
| return err; |
| } |
| |
| if (endp == NULL) { |
| return apr_pstrcat(cmd->pool, cmd->cmd->name, |
| "> directive missing closing '>'", NULL); |
| } |
| |
| args = apr_pstrndup(cmd->pool, arg, endp - arg); |
| |
| if (!args[0]) { |
| return apr_pstrcat(cmd->pool, cmd->cmd->name, |
| "> directive requires additional arguments", NULL); |
| } |
| |
| /* Pull the real provider name and the alias name from the block header */ |
| provider_name = ap_getword_conf(cmd->pool, &args); |
| provider_alias = ap_getword_conf(cmd->pool, &args); |
| provider_args = ap_getword_conf(cmd->pool, &args); |
| |
| if (!provider_name[0] || !provider_alias[0]) { |
| return apr_pstrcat(cmd->pool, cmd->cmd->name, |
| "> directive requires additional arguments", NULL); |
| } |
| |
| /* walk the subsection configuration to get the per_dir config that we will |
| merge just before the real provider is called. */ |
| cmd->override = OR_ALL|ACCESS_CONF; |
| errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_authz_config); |
| |
| if (!errmsg) { |
| provider_alias_rec *prvdraliasrec = apr_pcalloc(cmd->pool, sizeof(provider_alias_rec)); |
| const authz_provider *provider = ap_lookup_provider(AUTHZ_PROVIDER_GROUP, provider_name,"0"); |
| |
| /* Save off the new directory config along with the original provider name |
| and function pointer data */ |
| prvdraliasrec->sec_auth = new_authz_config; |
| prvdraliasrec->provider_name = provider_name; |
| prvdraliasrec->provider_alias = provider_alias; |
| prvdraliasrec->provider_args = provider_args; |
| prvdraliasrec->provider = provider; |
| |
| apr_hash_set(authcfg->alias_rec, provider_alias, APR_HASH_KEY_STRING, prvdraliasrec); |
| |
| /* Register the fake provider so that we get called first */ |
| ap_register_provider(cmd->pool, AUTHZ_PROVIDER_GROUP, provider_alias, "0", |
| &authz_alias_provider); |
| } |
| |
| cmd->override = old_overrides; |
| |
| return errmsg; |
| } |
| |
| static const char *authz_require_section(cmd_parms *cmd, void *mconfig, const char *arg) |
| { |
| int old_overrides = cmd->override; |
| const char *endp = ap_strrchr_c(arg, '>'); |
| const char *args; |
| const char *errmsg; |
| authz_request_state old_reqstate; |
| authz_core_dir_conf *conf = (authz_core_dir_conf*)mconfig; |
| // authz_core_srv_conf *authcfg = |
| // (authz_core_srv_conf *)ap_get_module_config(cmd->server->module_config, |
| // &authz_core_module); |
| |
| if (endp == NULL) { |
| return apr_pstrcat(cmd->pool, cmd->cmd->name, |
| "> directive missing closing '>'", NULL); |
| } |
| |
| args = apr_pstrndup(cmd->pool, arg, endp - arg); |
| |
| if (args[0]) { |
| return apr_pstrcat(cmd->pool, cmd->cmd->name, |
| "> directive doesn't take additional arguments", NULL); |
| } |
| |
| /* Save off the current request state so that we can go back to it after walking |
| the subsection. Indicate a transition in the logic incrementing the level. |
| After the subsection walk the level will be decremented to indicate the |
| path to follow. As the require directives are read by the configuration |
| the req_state and the level will allow it to traverse the list to find |
| the last element in the provider calling list. */ |
| old_reqstate = conf->req_state; |
| if (strcasecmp (cmd->directive->directive, "<SatisfyAll") == 0) { |
| conf->req_state = AUTHZ_REQSTATE_ALL; |
| } |
| else { |
| conf->req_state = AUTHZ_REQSTATE_ONE; |
| } |
| conf->req_state_level++; |
| cmd->override = OR_ALL|ACCESS_CONF; |
| |
| /* walk the subsection configuration to get the per_dir config that we will |
| merge just before the real provider is called. */ |
| errmsg = ap_walk_config(cmd->directive->first_child, cmd, cmd->context); |
| |
| conf->req_state_level--; |
| conf->req_state = old_reqstate; |
| cmd->override = old_overrides; |
| |
| return errmsg; |
| } |
| |
| static const command_rec authz_cmds[] = |
| { |
| AP_INIT_RAW_ARGS("Require", add_authz_provider, NULL, OR_AUTHCFG, |
| "Selects which authenticated users or groups may access " |
| "a protected space"), |
| AP_INIT_RAW_ARGS("Reject", add_authz_provider, (void*)1, OR_AUTHCFG, |
| "Rejects the specified authenticated users or groups from accessing " |
| "a protected space"), |
| AP_INIT_RAW_ARGS("<RequireAlias", authz_require_alias_section, NULL, RSRC_CONF, |
| "Container for authorization directives grouped under " |
| "an authz provider alias"), |
| AP_INIT_RAW_ARGS("<SatisfyAll", authz_require_section, NULL, OR_AUTHCFG, |
| "Container for grouping require statements that must all " |
| "succeed for authorization to be granted"), |
| AP_INIT_RAW_ARGS("<SatisfyOne", authz_require_section, NULL, OR_AUTHCFG, |
| "Container for grouping require statements of which one " |
| "must succeed for authorization to be granted"), |
| {NULL} |
| }; |
| |
| static authz_status check_provider_list (request_rec *r, authz_provider_list *current_provider, int prev_level) |
| { |
| authz_status auth_result = AUTHZ_DENIED; |
| |
| const authz_provider *provider; |
| |
| /* For now, if a provider isn't set, we'll be nice and use the file |
| * provider. |
| */ |
| if (!current_provider) { |
| provider = ap_lookup_provider(AUTHZ_PROVIDER_GROUP, |
| AUTHZ_DEFAULT_PROVIDER, "0"); |
| |
| if (!provider || !provider->check_authorization) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, |
| "No default authz provider configured"); |
| auth_result = AUTHZ_GENERAL_ERROR; |
| return auth_result; |
| } |
| apr_table_setn(r->notes, AUTHZ_PROVIDER_NAME_NOTE, |
| AUTHZ_DEFAULT_PROVIDER); |
| } |
| else { |
| provider = current_provider->provider; |
| apr_table_setn(r->notes, AUTHZ_PROVIDER_NAME_NOTE, |
| current_provider->provider_name); |
| } |
| |
| /* check to make sure that the request method requires |
| authorization before calling the provider */ |
| if (!(current_provider->method_mask & |
| (AP_METHOD_BIT << r->method_number))) { |
| return AUTHZ_DENIED; |
| } |
| |
| auth_result = provider->check_authorization(r, |
| current_provider->requirement); |
| |
| if (auth_result == AUTHZ_GENERAL_ERROR) { |
| return auth_result; |
| } |
| |
| if (current_provider->is_reject) { |
| auth_result = auth_result == AUTHZ_DENIED ? AUTHZ_GRANTED : AUTHZ_DENIED; |
| } |
| |
| apr_table_unset(r->notes, AUTHZ_PROVIDER_NAME_NOTE); |
| |
| /* If the current node is a Require_One type */ |
| if (current_provider->req_state == AUTHZ_REQSTATE_ONE) { |
| /* if the auth_result of *this* node was GRANTED and we are embedded in a Require_all block |
| then look to see if there is another Require_all node that needs to be satisfied */ |
| if (auth_result == AUTHZ_GRANTED) { |
| if ((current_provider->all_next) && |
| (current_provider->all_next->req_state_level < current_provider->req_state_level)) { |
| auth_result = check_provider_list (r, current_provider->all_next, |
| current_provider->req_state_level); |
| } |
| return auth_result; |
| } |
| one_next: |
| |
| /* Traverse forward to the next Require_one node it one exists |
| otherwise just return the auth_result */ |
| if (current_provider->one_next) { |
| auth_result = check_provider_list (r, current_provider->one_next, |
| current_provider->req_state_level); |
| } |
| else |
| return auth_result; |
| |
| /* if the *last* auth_result was GRANTED and we are embedded in a Require_all block |
| then look to see if there is another Require_all node that needs to be satisfied */ |
| if ((auth_result == AUTHZ_GRANTED) && (current_provider->all_next) && |
| (current_provider->all_next->req_state_level < current_provider->req_state_level)) { |
| auth_result = check_provider_list (r, current_provider->all_next, |
| current_provider->req_state_level); |
| } |
| /* If the *last* auth_result was DENIED and we are inside of a Require_one block |
| then look to see if there is another Require_one node that can be satisfied */ |
| else if ((auth_result == AUTHZ_DENIED) && (current_provider->one_next) && |
| (current_provider->one_next->req_state_level < current_provider->req_state_level)) { |
| goto one_next; |
| } |
| |
| return auth_result; |
| } |
| |
| /* If the current node is a Require_All type */ |
| if (current_provider->req_state == AUTHZ_REQSTATE_ALL) { |
| /* if the auth_result of *this* node was DENIED and we are embedded in a Require_one block |
| then look to see if there is another Require_one node that can be satisfied */ |
| if (auth_result == AUTHZ_DENIED) { |
| if ((current_provider->one_next) && |
| (current_provider->one_next->req_state_level < current_provider->req_state_level)) { |
| auth_result = check_provider_list (r, current_provider->one_next, |
| current_provider->req_state_level); |
| } |
| return auth_result; |
| } |
| all_next: |
| |
| /* Traverse forward to the next Require_all node it one exists |
| otherwise just return the auth_result */ |
| if (current_provider->all_next) { |
| auth_result = check_provider_list (r, current_provider->all_next, |
| current_provider->req_state_level); |
| } |
| else |
| return auth_result; |
| |
| /* if the *last* auth_result was DENIED and we are embedded in a Require_one block |
| then look to see if there is another Require_one node that can be satisfied */ |
| if ((auth_result == AUTHZ_DENIED) && (current_provider->one_next) && |
| (current_provider->one_next->req_state_level < current_provider->req_state_level)) { |
| auth_result = check_provider_list (r, current_provider->one_next, |
| current_provider->req_state_level); |
| } |
| /* If the *last* auth_result was GRANTED and we are inside of a Require_all block |
| then look to see if there is another Require_all node that needs to be satisfied */ |
| else if ((auth_result == AUTHZ_GRANTED) && (current_provider->all_next) && |
| (current_provider->all_next->req_state_level < current_provider->req_state_level)) { |
| goto all_next; |
| } |
| } |
| |
| return auth_result; |
| } |
| |
| static int authorize_user(request_rec *r) |
| { |
| authz_core_dir_conf *conf = ap_get_module_config(r->per_dir_config, |
| &authz_core_module); |
| authz_status auth_result; |
| authz_provider_list *current_provider; |
| |
| /* If we're not really configured for providers, stop now. */ |
| if (!conf->providers) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, |
| "no authorization providers configured"); |
| return DECLINED; |
| } |
| |
| current_provider = conf->providers; |
| |
| auth_result = check_provider_list (r, current_provider, 0); |
| |
| if (auth_result != AUTHZ_GRANTED) { |
| int return_code; |
| |
| switch (auth_result) { |
| case AUTHZ_DENIED: |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, |
| "user %s: authorization failure for \"%s\": ", |
| r->user, r->uri); |
| return_code = HTTP_UNAUTHORIZED; |
| break; |
| case AUTHZ_GENERAL_ERROR: |
| default: |
| /* We'll assume that the module has already said what its |
| * error was in the logs. |
| */ |
| return_code = HTTP_INTERNAL_SERVER_ERROR; |
| break; |
| } |
| |
| /* If we're returning 403, tell them to try again. */ |
| if (return_code == HTTP_UNAUTHORIZED) { |
| ap_note_auth_failure (r); |
| } |
| return return_code; |
| } |
| |
| return OK; |
| } |
| |
| static int authz_some_auth_required(request_rec *r) |
| { |
| authz_core_dir_conf *conf = ap_get_module_config(r->per_dir_config, |
| &authz_core_module); |
| authz_provider_list *current_provider; |
| int req_authz = 0; |
| |
| current_provider = conf->providers; |
| while (current_provider) { |
| |
| /* Does this provider config apply for this method */ |
| if (current_provider->method_mask & |
| (AP_METHOD_BIT << r->method_number)) { |
| req_authz = 1; |
| break; |
| } |
| |
| current_provider = current_provider->one_next; |
| } |
| |
| return req_authz; |
| } |
| |
| static void register_hooks(apr_pool_t *p) |
| { |
| APR_REGISTER_OPTIONAL_FN(authz_some_auth_required); |
| |
| ap_hook_auth_checker(authorize_user, NULL, NULL, APR_HOOK_MIDDLE); |
| } |
| |
| module AP_MODULE_DECLARE_DATA authz_core_module = |
| { |
| STANDARD20_MODULE_STUFF, |
| create_authz_core_dir_config, /* dir config creater */ |
| NULL, /* dir merger --- default is to override */ |
| create_authz_core_svr_config, /* server config */ |
| NULL, /* merge server config */ |
| authz_cmds, |
| register_hooks /* register hooks */ |
| }; |