]> granicus.if.org Git - apache/blob - modules/session/mod_session_crypto.c
Use ap_state_query() to fix many modules that were not correctly initializing
[apache] / modules / session / mod_session_crypto.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "mod_session.h"
18 #include "apu_version.h"
19 #include "apr_base64.h"                /* for apr_base64_decode et al */
20 #include "apr_lib.h"
21 #include "apr_strings.h"
22 #include "http_log.h"
23 #include "http_core.h"
24
25 #if APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION < 4
26
27 #error session_crypto_module requires APU v1.4.0 or later
28
29 #elif APU_HAVE_CRYPTO == 0
30
31 #error Crypto support must be enabled in APR
32
33 #else
34
35 #if APR_MAJOR_VERSION < 2
36 #define CRYPTO_VERSION 104
37 #else
38 #define CRYPTO_VERSION 200
39 #endif
40
41 #include "apr_crypto.h"                /* for apr_*_crypt et al */
42
43 #define LOG_PREFIX "mod_session_crypto: "
44 #define DRIVER_KEY "session_crypto_driver"
45
46 module AP_MODULE_DECLARE_DATA session_crypto_module;
47
48 /**
49  * Structure to carry the per-dir session config.
50  */
51 typedef struct {
52     const char *passphrase;
53     apr_array_header_t *params;
54     int passphrase_set;
55     apr_crypto_block_key_type_e cipher;
56     int cipher_set;
57 }session_crypto_dir_conf;
58
59 /**
60  * Structure to carry the server wide session config.
61  */
62 typedef struct {
63     const char *library;
64     apr_array_header_t *params;
65     int library_set;
66     int noinit;
67     int noinit_set;
68 }session_crypto_conf;
69
70 AP_DECLARE(int) ap_session_crypto_encode(request_rec * r, session_rec * z);
71 AP_DECLARE(int) ap_session_crypto_decode(request_rec * r, session_rec * z);
72 AP_DECLARE(int) ap_session_crypto_init(apr_pool_t *p, apr_pool_t *plog,
73         apr_pool_t *ptemp, server_rec *s);
74
75 /**
76  * Initialise the encryption as per the current config.
77  *
78  * Returns APR_SUCCESS if successful.
79  */
80 static apr_status_t crypt_init(request_rec * r,
81                                const apr_crypto_driver_t *driver,
82                                apr_crypto_t **f, apr_crypto_key_t **key,
83                                apr_uuid_t *salt, apr_size_t *ivSize,
84                                session_crypto_dir_conf * dconf)
85 {
86     apr_status_t res;
87
88     if (!driver) {
89         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX
90                 "encryption driver not configured, "
91                 "no SessionCryptoDriver set");
92         return APR_EGENERAL;
93     }
94
95     if (!dconf->passphrase_set) {
96         ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, LOG_PREFIX
97                 "encryption not configured, "
98                 "no passphrase set");
99         return APR_EGENERAL;
100     }
101
102     /* set up */
103     res = apr_crypto_make(driver, r->pool, dconf->params, f);
104     if (APR_ENOTIMPL == res) {
105         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
106                 "generic symmetrical encryption is not supported by this "
107                 "version of APR. session encryption not possible");
108     }
109
110     if (APR_SUCCESS == res) {
111 #if CRYPTO_VERSION < 200
112         res = apr_crypto_passphrase(driver, r->pool, *f, dconf->passphrase,
113 #else
114         res = apr_crypto_passphrase(r->pool, *f, dconf->passphrase,
115 #endif
116                 strlen(dconf->passphrase),
117                 (unsigned char *) salt, salt ? sizeof(apr_uuid_t) : 0,
118                 dconf->cipher, MODE_CBC, 1, 4096, key, ivSize);
119     }
120
121     if (APR_STATUS_IS_ENOKEY(res)) {
122         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
123                 "the passphrase '%s' was empty", dconf->passphrase);
124     }
125     if (APR_STATUS_IS_EPADDING(res)) {
126         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
127                 "padding is not supported for cipher");
128     }
129     if (APR_STATUS_IS_EKEYTYPE(res)) {
130         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
131                 "the key type is not known");
132     }
133     if (APR_SUCCESS != res) {
134         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
135                 "encryption could not be configured. Please check the "
136                 "certificates and/or passphrase as appropriate");
137         return APR_EGENERAL;
138     }
139
140     return APR_SUCCESS;
141 }
142
143 /**
144  * Encrypt the string given as per the current config.
145  *
146  * Returns APR_SUCCESS if successful.
147  */
148 static apr_status_t encrypt_string(request_rec * r,
149                                    const apr_crypto_driver_t *driver,
150                                    session_crypto_dir_conf *dconf,
151                                    const char *in, char **out)
152 {
153     apr_status_t res;
154     apr_crypto_t *f = NULL;
155     apr_crypto_key_t *key = NULL;
156     apr_size_t ivSize = 0;
157     apr_crypto_block_t *block = NULL;
158     unsigned char *encrypt = NULL;
159     unsigned char *combined = NULL;
160     apr_size_t encryptlen, tlen;
161     char *base64;
162     apr_size_t blockSize = 0;
163     const unsigned char *iv = NULL;
164     apr_uuid_t salt;
165
166     /* by default, return an empty string */
167     *out = "";
168
169     /* don't attempt to encrypt an empty string, trying to do so causes a segfault */
170     if (!in || !*in) {
171         return APR_SUCCESS;
172     }
173
174     /* use a uuid as a salt value, and prepend it to our result */
175     apr_uuid_get(&salt);
176     res = crypt_init(r, driver, &f, &key, &salt, &ivSize, dconf);
177     if (res != APR_SUCCESS) {
178         return res;
179     }
180
181 #if CRYPTO_VERSION < 200
182     res = apr_crypto_block_encrypt_init(driver, r->pool, f, key, &iv, &block,
183 #else
184     res = apr_crypto_block_encrypt_init(r->pool, f, key, &iv, &block,
185 #endif
186             &blockSize);
187     if (APR_SUCCESS != res) {
188         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
189                 "apr_crypto_block_encrypt_init failed");
190         return res;
191     }
192
193     /* encrypt the given string */
194 #if CRYPTO_VERSION < 200
195     res = apr_crypto_block_encrypt(driver, block, &encrypt,
196 #else
197     res = apr_crypto_block_encrypt(f, block, &encrypt,
198 #endif
199             &encryptlen, (unsigned char *)in, strlen(in));
200     if (APR_SUCCESS != res) {
201         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
202                 "apr_crypto_block_encrypt failed");
203         return res;
204     }
205 #if CRYPTO_VERSION < 200
206     res = apr_crypto_block_encrypt_finish(driver, block, encrypt + encryptlen,
207 #else
208     res = apr_crypto_block_encrypt_finish(f, block, encrypt + encryptlen,
209 #endif
210             &tlen);
211     if (APR_SUCCESS != res) {
212         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
213                 "apr_crypto_block_encrypt_finish failed");
214         return res;
215     }
216     encryptlen += tlen;
217
218     /* prepend the salt and the iv to the result */
219     combined = apr_palloc(r->pool, ivSize + encryptlen + sizeof(apr_uuid_t));
220     memcpy(combined, &salt, sizeof(apr_uuid_t));
221     memcpy(combined + sizeof(apr_uuid_t), iv, ivSize);
222     memcpy(combined + sizeof(apr_uuid_t) + ivSize, encrypt, encryptlen);
223
224     /* base64 encode the result */
225     base64 = apr_palloc(r->pool, apr_base64_encode_len(ivSize + encryptlen +
226                                                        sizeof(apr_uuid_t) + 1)
227                                  * sizeof(char));
228     apr_base64_encode(base64, (const char *) combined,
229                       ivSize + encryptlen + sizeof(apr_uuid_t));
230     *out = base64;
231
232     return res;
233
234 }
235
236 /**
237  * Decrypt the string given as per the current config.
238  *
239  * Returns APR_SUCCESS if successful.
240  */
241 static apr_status_t decrypt_string(request_rec * r,
242                                    const apr_crypto_driver_t *driver,
243                                    session_crypto_dir_conf *dconf,
244                                    const char *in, char **out)
245 {
246     apr_status_t res;
247     apr_crypto_t *f = NULL;
248     apr_crypto_key_t *key = NULL;
249     apr_size_t ivSize = 0;
250     apr_crypto_block_t *block = NULL;
251     unsigned char *decrypted = NULL;
252     apr_size_t decryptedlen, tlen;
253     apr_size_t decodedlen;
254     char *decoded;
255     apr_size_t blockSize = 0;
256
257     /* strip base64 from the string */
258     decoded = apr_palloc(r->pool, apr_base64_decode_len(in));
259     decodedlen = apr_base64_decode(decoded, in);
260     decoded[decodedlen] = '\0';
261
262     res = crypt_init(r, driver, &f, &key, (apr_uuid_t *)decoded, &ivSize, dconf);
263     if (res != APR_SUCCESS) {
264         return res;
265     }
266
267     /* sanity check - decoded too short? */
268     if (decodedlen < (sizeof(apr_uuid_t) + ivSize)) {
269         ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, LOG_PREFIX
270                 "too short to decrypt, skipping");
271         return APR_ECRYPT;
272     }
273
274     /* bypass the salt at the start of the decoded block */
275     decoded += sizeof(apr_uuid_t);
276     decodedlen -= sizeof(apr_uuid_t);
277
278 #if CRYPTO_VERSION < 200
279     res = apr_crypto_block_decrypt_init(driver, r->pool, f, key, (unsigned char *)decoded, &block,
280 #else
281     res = apr_crypto_block_decrypt_init(r->pool, f, key, (unsigned char *)decoded, &block,
282 #endif
283             &blockSize);
284     if (APR_SUCCESS != res) {
285         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
286                 "apr_crypto_block_decrypt_init failed");
287         return res;
288     }
289
290     /* bypass the iv at the start of the decoded block */
291     decoded += ivSize;
292     decodedlen -= ivSize;
293
294     /* decrypt the given string */
295 #if CRYPTO_VERSION < 200
296     res = apr_crypto_block_decrypt(driver, block, &decrypted,
297 #else
298     res = apr_crypto_block_decrypt(f, block, &decrypted,
299 #endif
300             &decryptedlen, (unsigned char *)decoded, decodedlen);
301     if (res) {
302         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
303                 "apr_crypto_block_decrypt failed");
304         return res;
305     }
306     *out = (char *) decrypted;
307
308 #if CRYPTO_VERSION < 200
309     res = apr_crypto_block_decrypt_finish(driver, block, decrypted + decryptedlen,
310 #else
311     res = apr_crypto_block_decrypt_finish(f, block, decrypted + decryptedlen,
312 #endif
313             &tlen);
314     if (APR_SUCCESS != res) {
315         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
316                 "apr_crypto_block_decrypt_finish failed");
317         return res;
318     }
319     decryptedlen += tlen;
320     decrypted[decryptedlen] = 0;
321
322     return APR_SUCCESS;
323
324 }
325
326 /**
327  * Crypto encoding for the session.
328  *
329  * @param r The request pointer.
330  * @param z A pointer to where the session will be written.
331  */
332 AP_DECLARE(int) ap_session_crypto_encode(request_rec * r, session_rec * z)
333 {
334
335     char *encoded = NULL;
336     apr_status_t res;
337     const apr_crypto_driver_t *driver = NULL;
338     session_crypto_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
339             &session_crypto_module);
340
341     if (dconf->passphrase_set && z->encoded && *z->encoded) {
342         apr_pool_userdata_get((void **)&driver, DRIVER_KEY, r->server->process->pconf);
343         res = encrypt_string(r, driver, dconf, z->encoded, &encoded);
344         if (res != OK) {
345             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, LOG_PREFIX
346                     "encrypt session failed");
347             return res;
348         }
349         z->encoded = encoded;
350     }
351
352     return OK;
353
354 }
355
356 /**
357  * Crypto decoding for the session.
358  *
359  * @param r The request pointer.
360  * @param z A pointer to where the session will be written.
361  */
362 AP_DECLARE(int) ap_session_crypto_decode(request_rec * r, session_rec * z)
363 {
364
365     char *encoded = NULL;
366     apr_status_t res;
367     const apr_crypto_driver_t *driver = NULL;
368     session_crypto_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
369             &session_crypto_module);
370
371     if ((dconf->passphrase_set) && z->encoded && *z->encoded) {
372         apr_pool_userdata_get((void **)&driver, DRIVER_KEY,
373                               r->server->process->pconf);
374         res = decrypt_string(r, driver, dconf, z->encoded, &encoded);
375         if (res != APR_SUCCESS) {
376             ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
377                     "decrypt session failed, wrong passphrase?");
378             return res;
379         }
380         z->encoded = encoded;
381     }
382
383     return OK;
384
385 }
386
387 /**
388  * Initialise the SSL in the post_config hook.
389  */
390 AP_DECLARE(int) ap_session_crypto_init(apr_pool_t *p, apr_pool_t *plog,
391         apr_pool_t *ptemp, server_rec *s)
392 {
393     const apr_crypto_driver_t *driver = NULL;
394
395     session_crypto_conf *conf = ap_get_module_config(s->module_config,
396             &session_crypto_module);
397
398     /* session_crypto_init() will be called twice. Don't bother
399      * going through all of the initialization on the first call
400      * because it will just be thrown away.*/
401     if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG)
402         return OK;
403
404     if (conf->library) {
405
406         const apu_err_t *err = NULL;
407         apr_status_t rv;
408
409         rv = apr_crypto_init(p, NULL);
410         if (APR_SUCCESS != rv) {
411             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, LOG_PREFIX
412                     "APR crypto could not be initialised");
413             return rv;
414         }
415
416         rv = apr_crypto_get_driver(p, conf->library, &driver, conf->params, &err);
417         if (APR_EREINIT == rv) {
418             if (!conf->noinit) {
419                 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, LOG_PREFIX
420                         "warning: crypto for '%s' was already initialised, "
421                         "using existing configuration", conf->library);
422             }
423             rv = APR_SUCCESS;
424         }
425         else {
426             if (conf->noinit) {
427                 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, LOG_PREFIX
428                         "warning: crypto for '%s' was not previously initialised "
429                         "when it was expected to be, initialised instead by "
430                         "mod_session_crypto", conf->library);
431             }
432         }
433         if (APR_SUCCESS != rv && err) {
434             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, LOG_PREFIX
435                     "%s", err->msg);
436             return rv;
437         }
438         if (APR_ENOTIMPL == rv) {
439             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, LOG_PREFIX
440                     "The crypto library '%s' could not be found",
441                     conf->library);
442             return rv;
443         }
444         if (APR_SUCCESS != rv || !driver) {
445             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, LOG_PREFIX
446                     "The crypto library '%s' could not be loaded",
447                     conf->library);
448             return rv;
449         }
450
451         ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, LOG_PREFIX
452                 "The crypto library '%s' was loaded successfully",
453                 conf->library);
454
455         apr_pool_userdata_set((const void *)driver, DRIVER_KEY,
456                 apr_pool_cleanup_null, s->process->pconf);
457
458     }
459
460     return OK;
461 }
462
463 static void *create_session_crypto_config(apr_pool_t * p, server_rec *s)
464 {
465     session_crypto_conf *new =
466     (session_crypto_conf *) apr_pcalloc(p, sizeof(session_crypto_conf));
467
468     /* if no library has been configured, set the recommended library
469      * as a sensible default.
470      */
471 #ifdef APU_CRYPTO_RECOMMENDED_DRIVER
472     new->library = APU_CRYPTO_RECOMMENDED_DRIVER;
473 #endif
474
475     return (void *) new;
476 }
477
478 static void *create_session_crypto_dir_config(apr_pool_t * p, char *dummy)
479 {
480     session_crypto_dir_conf *new =
481     (session_crypto_dir_conf *) apr_pcalloc(p, sizeof(session_crypto_dir_conf));
482
483     /* default cipher AES256-SHA */
484     new->cipher = KEY_AES_256;
485
486     return (void *) new;
487 }
488
489 static void *merge_session_crypto_dir_config(apr_pool_t * p, void *basev, void *addv)
490 {
491     session_crypto_dir_conf *new = (session_crypto_dir_conf *) apr_pcalloc(p, sizeof(session_crypto_dir_conf));
492     session_crypto_dir_conf *add = (session_crypto_dir_conf *) addv;
493     session_crypto_dir_conf *base = (session_crypto_dir_conf *) basev;
494
495     new->passphrase = (add->passphrase_set == 0) ? base->passphrase : add->passphrase;
496     new->params = (add->passphrase_set == 0) ? base->params : add->params;
497     new->passphrase_set = add->passphrase_set || base->passphrase_set;
498     new->cipher = (add->cipher_set == 0) ? base->cipher : add->cipher;
499     new->cipher_set = add->cipher_set || base->cipher_set;
500
501     return new;
502 }
503
504 static const char *set_crypto_driver(cmd_parms * cmd, void *config, const char *arg)
505 {
506     char *word, *val;
507     int library_set = 0;
508     session_crypto_conf *conf =
509         (session_crypto_conf *)ap_get_module_config(cmd->server->module_config,
510             &session_crypto_module);
511     apr_crypto_param_t *param;
512
513     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
514
515     if (err != NULL) {
516         return err;
517     }
518
519     conf->params = apr_array_make(cmd->pool, 10, sizeof(apr_crypto_param_t));
520
521     while (*arg) {
522         word = ap_getword_conf(cmd->pool, &arg);
523         val = strchr(word, '=');
524         if (!val) {
525             if (!strcasecmp(word, "noinit")) {
526                 conf->noinit = 1;
527                 conf->noinit_set = 1;
528             }
529             else if (!library_set) {
530                 conf->library = word;
531                 conf->library_set = 1;
532                 library_set = 1;
533             }
534             else {
535                 return "Invalid SessionCryptoDriver parameter. Parameter must "
536                 "be in the form 'key=value'.";
537             }
538         }
539         else {
540             *val++ = '\0';
541             if (!strcasecmp(word, "dir")) {
542                 param = apr_array_push(conf->params);
543                 param->type = APR_CRYPTO_CA_TYPE_DIR;
544                 param->path = val;
545             }
546             else if (!strcasecmp(word, "key3")) {
547                 param = apr_array_push(conf->params);
548                 param->type = APR_CRYPTO_CERT_TYPE_KEY3_DB;
549                 param->path = val;
550             }
551             else if (!strcasecmp(word, "cert7")) {
552                 param = apr_array_push(conf->params);
553                 param->type = APR_CRYPTO_CA_TYPE_CERT7_DB;
554                 param->path = val;
555             }
556             else if (!strcasecmp(word, "secmod")) {
557                 param = apr_array_push(conf->params);
558                 param->type = APR_CRYPTO_CA_TYPE_SECMOD;
559                 param->path = val;
560             }
561         }
562     }
563
564     return NULL;
565 }
566
567 static const char *set_crypto_passphrase(cmd_parms * cmd, void *config, const char *arg)
568 {
569     char *word, *val;
570     int passphrase_set = 0;
571     session_crypto_dir_conf *dconf = (session_crypto_dir_conf *) config;
572     apr_crypto_param_t *param;
573     dconf->params = apr_array_make(cmd->pool, 10, sizeof(apr_crypto_param_t));
574
575     while (*arg) {
576         word = ap_getword_conf(cmd->pool, &arg);
577         val = strchr(word, '=');
578         if (!val) {
579             if (!passphrase_set) {
580                 dconf->passphrase = word;
581                 dconf->passphrase_set = 1;
582                 passphrase_set = 1;
583             }
584             else {
585                 return "Invalid SessionCryptoPassphrase parameter. Parameter must "
586                 "be in the form 'key=value'.";
587             }
588         }
589         else {
590             *val++ = '\0';
591             if (!strcasecmp(word, "engine")) {
592                 param = apr_array_push(dconf->params);
593                 param->type = APR_CRYPTO_ENGINE;
594                 param->path = val;
595             }
596             else if (!strcasecmp(word, "cipher")) {
597                 if (!strcasecmp(val, "3des192")) {
598                     dconf->cipher = KEY_3DES_192;
599                     dconf->cipher_set = 1;
600                 }
601                 else if (!strcasecmp(val, "aes256")) {
602                     dconf->cipher = KEY_AES_256;
603                     dconf->cipher_set = 1;
604                 }
605                 else {
606                     return "Invalid SessionCryptoPassphrase parameter. Cipher must "
607                     "be '3des192' or 'aes256'.";
608                 }
609             }
610             else {
611                 return "Invalid SessionCryptoPassphrase parameter. Parameters must "
612                 "be 'engine' or 'cipher'.";
613             }
614         }
615     }
616
617     return NULL;
618 }
619
620 static const command_rec session_crypto_cmds[] =
621 {
622     AP_INIT_RAW_ARGS("SessionCryptoPassphrase", set_crypto_passphrase, NULL, RSRC_CONF|OR_AUTHCFG,
623             "The passphrase used to encrypt the session"),
624     AP_INIT_RAW_ARGS("SessionCryptoDriver", set_crypto_driver, NULL, RSRC_CONF,
625             "The underlying crypto library driver to use"),
626     {    NULL}
627 };
628
629 static void register_hooks(apr_pool_t * p)
630 {
631     ap_hook_session_encode(ap_session_crypto_encode, NULL, NULL, APR_HOOK_LAST);
632     ap_hook_session_decode(ap_session_crypto_decode, NULL, NULL, APR_HOOK_FIRST);
633     ap_hook_post_config(ap_session_crypto_init, NULL, NULL, APR_HOOK_FIRST);
634 }
635
636 AP_DECLARE_MODULE(session_crypto) =
637 {
638     STANDARD20_MODULE_STUFF,
639     create_session_crypto_dir_config, /* dir config creater */
640     merge_session_crypto_dir_config,  /* dir merger --- default is to
641                                        * override */
642     create_session_crypto_config,     /* server config */
643     NULL,                             /* merge server config */
644     session_crypto_cmds,              /* command apr_table_t */
645     register_hooks                    /* register hooks */
646 };
647
648 #endif