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 #include "apr_crypto.h"                /* for apr_*_crypt et al */
36 
37 #define CRYPTO_KEY "session_crypto_context"
38 
39 module AP_MODULE_DECLARE_DATA session_crypto_module;
40 
41 /**
42  * Structure to carry the per-dir session config.
43  */
44 typedef struct {
45     apr_array_header_t *passphrases;
46     int passphrases_set;
47     const char *cipher;
48     int cipher_set;
49 } session_crypto_dir_conf;
50 
51 /**
52  * Structure to carry the server wide session config.
53  */
54 typedef struct {
55     const char *library;
56     const char *params;
57     int library_set;
58 } session_crypto_conf;
59 
60 /**
61  * Initialise the encryption as per the current config.
62  *
63  * Returns APR_SUCCESS if successful.
64  */
crypt_init(request_rec * r,const apr_crypto_t * f,apr_crypto_block_key_type_e ** cipher,session_crypto_dir_conf * dconf)65 static apr_status_t crypt_init(request_rec *r,
66         const apr_crypto_t *f, apr_crypto_block_key_type_e **cipher,
67         session_crypto_dir_conf * dconf)
68 {
69     apr_status_t res;
70     apr_hash_t *ciphers;
71 
72     res = apr_crypto_get_block_key_types(&ciphers, f);
73     if (APR_SUCCESS != res) {
74         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01823)
75                 "no ciphers returned by APR. "
76                 "session encryption not possible");
77         return res;
78     }
79 
80     *cipher = apr_hash_get(ciphers, dconf->cipher, APR_HASH_KEY_STRING);
81     if (!(*cipher)) {
82         apr_hash_index_t *hi;
83         const void *key;
84         apr_ssize_t klen;
85         int sum = 0;
86         int offset = 0;
87         char *options = NULL;
88 
89         for (hi = apr_hash_first(r->pool, ciphers); hi; hi = apr_hash_next(hi)) {
90             apr_hash_this(hi, NULL, &klen, NULL);
91             sum += klen + 2;
92         }
93         for (hi = apr_hash_first(r->pool, ciphers); hi; hi = apr_hash_next(hi)) {
94             apr_hash_this(hi, &key, &klen, NULL);
95             if (!options) {
96                 options = apr_palloc(r->pool, sum + 1);
97             }
98             else {
99                 options[offset++] = ',';
100                 options[offset++] = ' ';
101             }
102             strncpy(options + offset, key, klen);
103             offset += klen;
104         }
105         options[offset] = 0;
106 
107         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01824)
108                 "cipher '%s' not recognised by crypto driver. "
109                 "session encryption not possible, options: %s", dconf->cipher, options);
110 
111         return APR_EGENERAL;
112     }
113 
114     return APR_SUCCESS;
115 }
116 
117 /**
118  * Encrypt the string given as per the current config.
119  *
120  * Returns APR_SUCCESS if successful.
121  */
encrypt_string(request_rec * r,const apr_crypto_t * f,session_crypto_dir_conf * dconf,const char * in,char ** out)122 static apr_status_t encrypt_string(request_rec * r, const apr_crypto_t *f,
123         session_crypto_dir_conf *dconf, const char *in, char **out)
124 {
125     apr_status_t res;
126     apr_crypto_key_t *key = NULL;
127     apr_size_t ivSize = 0;
128     apr_crypto_block_t *block = NULL;
129     unsigned char *encrypt = NULL;
130     unsigned char *combined = NULL;
131     apr_size_t encryptlen, tlen;
132     char *base64;
133     apr_size_t blockSize = 0;
134     const unsigned char *iv = NULL;
135     apr_uuid_t salt;
136     apr_crypto_block_key_type_e *cipher;
137     const char *passphrase;
138 
139     /* by default, return an empty string */
140     *out = "";
141 
142     /* don't attempt to encrypt an empty string, trying to do so causes a segfault */
143     if (!in || !*in) {
144         return APR_SUCCESS;
145     }
146 
147     /* use a uuid as a salt value, and prepend it to our result */
148     apr_uuid_get(&salt);
149     res = crypt_init(r, f, &cipher, dconf);
150     if (res != APR_SUCCESS) {
151         return res;
152     }
153 
154     /* encrypt using the first passphrase in the list */
155     passphrase = APR_ARRAY_IDX(dconf->passphrases, 0, char *);
156     res = apr_crypto_passphrase(&key, &ivSize, passphrase,
157             strlen(passphrase),
158             (unsigned char *) (&salt), sizeof(apr_uuid_t),
159             *cipher, APR_MODE_CBC, 1, 4096, f, r->pool);
160     if (APR_STATUS_IS_ENOKEY(res)) {
161         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01825)
162                 "the passphrase '%s' was empty", passphrase);
163     }
164     if (APR_STATUS_IS_EPADDING(res)) {
165         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01826)
166                 "padding is not supported for cipher");
167     }
168     if (APR_STATUS_IS_EKEYTYPE(res)) {
169         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01827)
170                 "the key type is not known");
171     }
172     if (APR_SUCCESS != res) {
173         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01828)
174                 "encryption could not be configured.");
175         return res;
176     }
177 
178     res = apr_crypto_block_encrypt_init(&block, &iv, key, &blockSize, r->pool);
179     if (APR_SUCCESS != res) {
180         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01829)
181                 "apr_crypto_block_encrypt_init failed");
182         return res;
183     }
184 
185     /* encrypt the given string */
186     res = apr_crypto_block_encrypt(&encrypt, &encryptlen, (unsigned char *)in,
187             strlen(in), block);
188     if (APR_SUCCESS != res) {
189         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01830)
190                 "apr_crypto_block_encrypt failed");
191         return res;
192     }
193     res = apr_crypto_block_encrypt_finish(encrypt + encryptlen, &tlen, block);
194     if (APR_SUCCESS != res) {
195         ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01831)
196                 "apr_crypto_block_encrypt_finish failed");
197         return res;
198     }
199     encryptlen += tlen;
200 
201     /* prepend the salt and the iv to the result */
202     combined = apr_palloc(r->pool, ivSize + encryptlen + sizeof(apr_uuid_t));
203     memcpy(combined, &salt, sizeof(apr_uuid_t));
204     memcpy(combined + sizeof(apr_uuid_t), iv, ivSize);
205     memcpy(combined + sizeof(apr_uuid_t) + ivSize, encrypt, encryptlen);
206 
207     /* base64 encode the result */
208     base64 = apr_palloc(r->pool, apr_base64_encode_len(ivSize + encryptlen +
209                     sizeof(apr_uuid_t) + 1)
210             * sizeof(char));
211     apr_base64_encode(base64, (const char *) combined,
212             ivSize + encryptlen + sizeof(apr_uuid_t));
213     *out = base64;
214 
215     return res;
216 
217 }
218 
219 /**
220  * Decrypt the string given as per the current config.
221  *
222  * Returns APR_SUCCESS if successful.
223  */
decrypt_string(request_rec * r,const apr_crypto_t * f,session_crypto_dir_conf * dconf,const char * in,char ** out)224 static apr_status_t decrypt_string(request_rec * r, const apr_crypto_t *f,
225         session_crypto_dir_conf *dconf, const char *in, char **out)
226 {
227     apr_status_t res;
228     apr_crypto_key_t *key = NULL;
229     apr_size_t ivSize = 0;
230     apr_crypto_block_t *block = NULL;
231     unsigned char *decrypted = NULL;
232     apr_size_t decryptedlen, tlen;
233     apr_size_t decodedlen;
234     char *decoded;
235     apr_size_t blockSize = 0;
236     apr_crypto_block_key_type_e *cipher;
237     int i = 0;
238 
239     /* strip base64 from the string */
240     decoded = apr_palloc(r->pool, apr_base64_decode_len(in));
241     decodedlen = apr_base64_decode(decoded, in);
242     decoded[decodedlen] = '\0';
243 
244     res = crypt_init(r, f, &cipher, dconf);
245     if (res != APR_SUCCESS) {
246         return res;
247     }
248 
249     /* try each passphrase in turn */
250     for (; i < dconf->passphrases->nelts; i++) {
251         const char *passphrase = APR_ARRAY_IDX(dconf->passphrases, i, char *);
252         apr_size_t len = decodedlen;
253         char *slider = decoded;
254 
255         /* encrypt using the first passphrase in the list */
256         res = apr_crypto_passphrase(&key, &ivSize, passphrase,
257                 strlen(passphrase),
258                 (unsigned char *)decoded, sizeof(apr_uuid_t),
259                 *cipher, APR_MODE_CBC, 1, 4096, f, r->pool);
260         if (APR_STATUS_IS_ENOKEY(res)) {
261             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01832)
262                     "the passphrase '%s' was empty", passphrase);
263             continue;
264         }
265         else if (APR_STATUS_IS_EPADDING(res)) {
266             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01833)
267                     "padding is not supported for cipher");
268             continue;
269         }
270         else if (APR_STATUS_IS_EKEYTYPE(res)) {
271             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01834)
272                     "the key type is not known");
273             continue;
274         }
275         else if (APR_SUCCESS != res) {
276             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01835)
277                     "encryption could not be configured.");
278             continue;
279         }
280 
281         /* sanity check - decoded too short? */
282         if (decodedlen < (sizeof(apr_uuid_t) + ivSize)) {
283             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(01836)
284                     "too short to decrypt, skipping");
285             res = APR_ECRYPT;
286             continue;
287         }
288 
289         /* bypass the salt at the start of the decoded block */
290         slider += sizeof(apr_uuid_t);
291         len -= sizeof(apr_uuid_t);
292 
293         res = apr_crypto_block_decrypt_init(&block, &blockSize, (unsigned char *)slider, key,
294                 r->pool);
295         if (APR_SUCCESS != res) {
296             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01837)
297                     "apr_crypto_block_decrypt_init failed");
298             continue;
299         }
300 
301         /* bypass the iv at the start of the decoded block */
302         slider += ivSize;
303         len -= ivSize;
304 
305         /* decrypt the given string */
306         res = apr_crypto_block_decrypt(&decrypted, &decryptedlen,
307                 (unsigned char *)slider, len, block);
308         if (res) {
309             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01838)
310                     "apr_crypto_block_decrypt failed");
311             continue;
312         }
313         *out = (char *) decrypted;
314 
315         res = apr_crypto_block_decrypt_finish(decrypted + decryptedlen, &tlen, block);
316         if (APR_SUCCESS != res) {
317             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01839)
318                     "apr_crypto_block_decrypt_finish failed");
319             continue;
320         }
321         decryptedlen += tlen;
322         decrypted[decryptedlen] = 0;
323 
324         break;
325     }
326 
327     if (APR_SUCCESS != res) {
328         ap_log_rerror(APLOG_MARK, APLOG_INFO, res, r, APLOGNO(01840)
329                 "decryption failed");
330     }
331 
332     return res;
333 
334 }
335 
336 /**
337  * Crypto encoding for the session.
338  *
339  * @param r The request pointer.
340  * @param z A pointer to where the session will be written.
341  */
session_crypto_encode(request_rec * r,session_rec * z)342 static apr_status_t session_crypto_encode(request_rec * r, session_rec * z)
343 {
344 
345     char *encoded = NULL;
346     apr_status_t res;
347     const apr_crypto_t *f = NULL;
348     session_crypto_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
349             &session_crypto_module);
350 
351     if (dconf->passphrases_set && z->encoded && *z->encoded) {
352         apr_pool_userdata_get((void **)&f, CRYPTO_KEY, r->server->process->pconf);
353         res = encrypt_string(r, f, dconf, z->encoded, &encoded);
354         if (res != OK) {
355             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01841)
356                     "encrypt session failed");
357             return res;
358         }
359         z->encoded = encoded;
360     }
361 
362     return OK;
363 
364 }
365 
366 /**
367  * Crypto decoding for the session.
368  *
369  * @param r The request pointer.
370  * @param z A pointer to where the session will be written.
371  */
session_crypto_decode(request_rec * r,session_rec * z)372 static apr_status_t session_crypto_decode(request_rec * r,
373         session_rec * z)
374 {
375 
376     char *encoded = NULL;
377     apr_status_t res;
378     const apr_crypto_t *f = NULL;
379     session_crypto_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
380             &session_crypto_module);
381 
382     if ((dconf->passphrases_set) && z->encoded && *z->encoded) {
383         apr_pool_userdata_get((void **)&f, CRYPTO_KEY,
384                 r->server->process->pconf);
385         res = decrypt_string(r, f, dconf, z->encoded, &encoded);
386         if (res != APR_SUCCESS) {
387             ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01842)
388                     "decrypt session failed, wrong passphrase?");
389             return res;
390         }
391         z->encoded = encoded;
392     }
393 
394     return OK;
395 
396 }
397 
398 /**
399  * Initialise the SSL in the post_config hook.
400  */
session_crypto_init(apr_pool_t * p,apr_pool_t * plog,apr_pool_t * ptemp,server_rec * s)401 static int session_crypto_init(apr_pool_t *p, apr_pool_t *plog,
402         apr_pool_t *ptemp, server_rec *s)
403 {
404     const apr_crypto_driver_t *driver = NULL;
405     apr_crypto_t *f = NULL;
406 
407     session_crypto_conf *conf = ap_get_module_config(s->module_config,
408             &session_crypto_module);
409 
410     /* session_crypto_init() will be called twice. Don't bother
411      * going through all of the initialization on the first call
412      * because it will just be thrown away.*/
413     if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
414         return OK;
415     }
416 
417     if (conf->library) {
418 
419         const apu_err_t *err = NULL;
420         apr_status_t rv;
421 
422         rv = apr_crypto_init(p);
423         if (APR_SUCCESS != rv) {
424             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01843)
425                     "APR crypto could not be initialised");
426             return rv;
427         }
428 
429         rv = apr_crypto_get_driver(&driver, conf->library, conf->params, &err, p);
430         if (APR_EREINIT == rv) {
431             ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, APLOGNO(01844)
432                     "warning: crypto for '%s' was already initialised, "
433                     "using existing configuration", conf->library);
434             rv = APR_SUCCESS;
435         }
436         if (APR_SUCCESS != rv && err) {
437             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01845)
438                     "The crypto library '%s' could not be loaded: %s (%s: %d)", conf->library, err->msg, err->reason, err->rc);
439             return rv;
440         }
441         if (APR_ENOTIMPL == rv) {
442             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01846)
443                     "The crypto library '%s' could not be found",
444                     conf->library);
445             return rv;
446         }
447         if (APR_SUCCESS != rv || !driver) {
448             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01847)
449                     "The crypto library '%s' could not be loaded",
450                     conf->library);
451             return rv;
452         }
453 
454         rv = apr_crypto_make(&f, driver, conf->params, p);
455         if (APR_SUCCESS != rv) {
456             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01848)
457                     "The crypto library '%s' could not be initialised",
458                     conf->library);
459             return rv;
460         }
461 
462         ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(01849)
463                 "The crypto library '%s' was loaded successfully",
464                 conf->library);
465 
466         apr_pool_userdata_set((const void *)f, CRYPTO_KEY,
467                 apr_pool_cleanup_null, s->process->pconf);
468 
469     }
470 
471     return OK;
472 }
473 
create_session_crypto_config(apr_pool_t * p,server_rec * s)474 static void *create_session_crypto_config(apr_pool_t * p, server_rec *s)
475 {
476     session_crypto_conf *new =
477     (session_crypto_conf *) apr_pcalloc(p, sizeof(session_crypto_conf));
478 
479     /* if no library has been configured, set the recommended library
480      * as a sensible default.
481      */
482 #ifdef APU_CRYPTO_RECOMMENDED_DRIVER
483     new->library = APU_CRYPTO_RECOMMENDED_DRIVER;
484 #endif
485 
486     return (void *) new;
487 }
488 
create_session_crypto_dir_config(apr_pool_t * p,char * dummy)489 static void *create_session_crypto_dir_config(apr_pool_t * p, char *dummy)
490 {
491     session_crypto_dir_conf *new =
492     (session_crypto_dir_conf *) apr_pcalloc(p, sizeof(session_crypto_dir_conf));
493 
494     new->passphrases = apr_array_make(p, 10, sizeof(char *));
495 
496     /* default cipher AES256-SHA */
497     new->cipher = "aes256";
498 
499     return (void *) new;
500 }
501 
merge_session_crypto_dir_config(apr_pool_t * p,void * basev,void * addv)502 static void *merge_session_crypto_dir_config(apr_pool_t * p, void *basev, void *addv)
503 {
504     session_crypto_dir_conf *new = (session_crypto_dir_conf *) apr_pcalloc(p, sizeof(session_crypto_dir_conf));
505     session_crypto_dir_conf *add = (session_crypto_dir_conf *) addv;
506     session_crypto_dir_conf *base = (session_crypto_dir_conf *) basev;
507 
508     new->passphrases = (add->passphrases_set == 0) ? base->passphrases : add->passphrases;
509     new->passphrases_set = add->passphrases_set || base->passphrases_set;
510     new->cipher = (add->cipher_set == 0) ? base->cipher : add->cipher;
511     new->cipher_set = add->cipher_set || base->cipher_set;
512 
513     return new;
514 }
515 
set_crypto_driver(cmd_parms * cmd,void * config,const char * arg)516 static const char *set_crypto_driver(cmd_parms * cmd, void *config, const char *arg)
517 {
518     session_crypto_conf *conf =
519     (session_crypto_conf *)ap_get_module_config(cmd->server->module_config,
520             &session_crypto_module);
521 
522     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
523 
524     if (err != NULL) {
525         return err;
526     }
527 
528     conf->library = ap_getword_conf(cmd->pool, &arg);
529     conf->params = arg;
530     conf->library_set = 1;
531 
532     return NULL;
533 }
534 
set_crypto_passphrase(cmd_parms * cmd,void * config,const char * arg)535 static const char *set_crypto_passphrase(cmd_parms * cmd, void *config, const char *arg)
536 {
537     const char **passphrase;
538     session_crypto_dir_conf *dconf = (session_crypto_dir_conf *) config;
539 
540     passphrase = apr_array_push(dconf->passphrases);
541     *passphrase = arg;
542     dconf->passphrases_set = 1;
543 
544     return NULL;
545 }
546 
set_crypto_passphrase_file(cmd_parms * cmd,void * config,const char * filename)547 static const char *set_crypto_passphrase_file(cmd_parms *cmd, void *config,
548                                   const char *filename)
549 {
550     char buffer[MAX_STRING_LEN];
551     char *arg;
552     const char *args;
553     ap_configfile_t *file;
554     apr_status_t rv;
555 
556     filename = ap_server_root_relative(cmd->temp_pool, filename);
557     rv = ap_pcfg_openfile(&file, cmd->temp_pool, filename);
558     if (rv != APR_SUCCESS) {
559         return apr_psprintf(cmd->pool, "%s: Could not open file %s: %pm",
560                             cmd->cmd->name, filename, &rv);
561     }
562 
563     while (!(ap_cfg_getline(buffer, sizeof(buffer), file))) {
564         args = buffer;
565         while (*(arg = ap_getword_conf(cmd->pool, &args)) != '\0') {
566             if (*arg == '#' || *arg == 0) {
567                 break;
568             }
569             set_crypto_passphrase(cmd, config, arg);
570         }
571     }
572 
573     ap_cfg_closefile(file);
574 
575     return NULL;
576 }
577 
set_crypto_cipher(cmd_parms * cmd,void * config,const char * cipher)578 static const char *set_crypto_cipher(cmd_parms * cmd, void *config, const char *cipher)
579 {
580     session_crypto_dir_conf *dconf = (session_crypto_dir_conf *) config;
581 
582     dconf->cipher = cipher;
583     dconf->cipher_set = 1;
584 
585     return NULL;
586 }
587 
588 static const command_rec session_crypto_cmds[] =
589 {
590     AP_INIT_ITERATE("SessionCryptoPassphrase", set_crypto_passphrase, NULL, RSRC_CONF|OR_AUTHCFG,
591             "The passphrase(s) used to encrypt the session. First will be used for encryption, all phrases will be accepted for decryption"),
592     AP_INIT_TAKE1("SessionCryptoPassphraseFile", set_crypto_passphrase_file, NULL, RSRC_CONF|ACCESS_CONF,
593             "File containing passphrase(s) used to encrypt the session, one per line. First will be used for encryption, all phrases will be accepted for decryption"),
594     AP_INIT_TAKE1("SessionCryptoCipher", set_crypto_cipher, NULL, RSRC_CONF|OR_AUTHCFG,
595             "The underlying crypto cipher to use"),
596     AP_INIT_RAW_ARGS("SessionCryptoDriver", set_crypto_driver, NULL, RSRC_CONF,
597             "The underlying crypto library driver to use"),
598     { NULL }
599 };
600 
register_hooks(apr_pool_t * p)601 static void register_hooks(apr_pool_t * p)
602 {
603     ap_hook_session_encode(session_crypto_encode, NULL, NULL, APR_HOOK_LAST);
604     ap_hook_session_decode(session_crypto_decode, NULL, NULL, APR_HOOK_FIRST);
605     ap_hook_post_config(session_crypto_init, NULL, NULL, APR_HOOK_LAST);
606 }
607 
608 AP_DECLARE_MODULE(session_crypto) =
609 {
610     STANDARD20_MODULE_STUFF,
611     create_session_crypto_dir_config, /* dir config creater */
612     merge_session_crypto_dir_config, /* dir merger --- default is to override */
613     create_session_crypto_config, /* server config */
614     NULL, /* merge server config */
615     session_crypto_cmds, /* command apr_table_t */
616     register_hooks /* register hooks */
617 };
618 
619 #endif
620