K5 only
[hcoop/zz_old/modwaklog.git] / mod_waklog.c
1 #include "httpd.h"
2 #include "http_config.h"
3 #include "http_conf_globals.h"
4 #include "http_log.h"
5 #include "http_protocol.h"
6 #include "http_request.h"
7 #include "http_core.h"
8 #include "ap_config.h"
9 #include <krb5.h>
10
11 #if defined(sun)
12 #include <sys/ioccom.h>
13 #endif /* sun */
14 #include <stropts.h>
15 #include <kerberosIV/krb.h>
16 #include <kerberosIV/des.h>
17 #include <afs/venus.h>
18 #include <afs/auth.h>
19 #include <rx/rxkad.h>
20
21 #include <asm/bitops.h>
22 #include <sys/shm.h>
23
24 #define KEYTAB "/home/drh/keytab.umweb.drhtest"
25 #define KEYTAB_PRINCIPAL "umweb/drhtest"
26
27 #define TKT_LIFE 10*60*60
28 #define SLEEP_TIME 5*60 /* should be TKT_LIFE */
29
30 #define AFS_CELL "umich.edu" /* NB: lower case */
31
32 #define K5PATH "FILE:/tmp/waklog.creds.k5"
33 #define K4PATH "/tmp/waklog.creds.k4"
34
35 module waklog_module;
36
37 struct ClearToken {
38 long AuthHandle;
39 char HandShakeKey[ 8 ];
40 long ViceId;
41 long BeginTimestamp;
42 long EndTimestamp;
43 };
44
45 typedef struct {
46 int configured;
47 int protect;
48 char *keytab;
49 char *keytab_principal;
50 char *afs_cell;
51 } waklog_host_config;
52
53 typedef struct {
54 struct ktc_token token;
55 } waklog_child_config;
56 waklog_child_config child;
57
58 void
59 bin_dump( char *s, char *cp, int count )
60 {
61 char *buffer;
62 char c;
63 int w;
64 int i;
65 long o;
66
67 o = 0;
68 buffer = cp;
69 while ( count > 0 ) {
70 c = 16;
71 if (c > count) {
72 c = count;
73 }
74 sprintf( s, "%05lx:", o);
75 s += strlen(s);
76 w = 0;
77 for (i = 0; i < c/2; ++i) {
78 w += 5;
79 sprintf( s, " %04x", ((unsigned short *)buffer)[i]);
80 s += strlen(s);
81 }
82 if (c & 1) {
83 w += 3;
84 sprintf( s, " %02x", buffer[c-1]);
85 s += strlen(s);
86 }
87 while (w < 41) {
88 ++w;
89 sprintf( s, "%c", ' ');
90 s += strlen(s);
91 }
92 for (i = 0; i < c; ++i) {
93 if (isprint(buffer[i])) {
94 sprintf( s, "%c", buffer[i]);
95 } else {
96 sprintf( s, ".");
97 }
98 s += strlen(s);
99 }
100 sprintf( s, "\n" );
101 s += strlen(s);
102 o += c;
103 buffer += c;
104 count -= c;
105 }
106 sprintf( s, "%05lx:\0", o );
107 }
108
109
110 static void *
111 waklog_create_dir_config( pool *p, char *path )
112 {
113 waklog_host_config *cfg;
114
115 cfg = (waklog_host_config *)ap_pcalloc( p, sizeof( waklog_host_config ));
116 cfg->configured = 0;
117 cfg->protect = 0;
118 cfg->keytab = KEYTAB;
119 cfg->keytab_principal = KEYTAB_PRINCIPAL;
120 cfg->afs_cell = AFS_CELL;
121
122 return( cfg );
123 }
124
125
126 static void *
127 waklog_create_server_config( pool *p, server_rec *s )
128 {
129 waklog_host_config *cfg;
130
131 cfg = (waklog_host_config *)ap_pcalloc( p, sizeof( waklog_host_config ));
132 cfg->configured = 0;
133 cfg->protect = 0;
134 cfg->keytab = KEYTAB;
135 cfg->keytab_principal = KEYTAB_PRINCIPAL;
136 cfg->afs_cell = AFS_CELL;
137
138 return( cfg );
139 }
140
141
142 static const char *
143 set_waklog_protect( cmd_parms *params, void *mconfig, int flag )
144 {
145 waklog_host_config *cfg;
146
147 if ( params->path == NULL ) {
148 cfg = (waklog_host_config *) ap_get_module_config(
149 params->server->module_config, &waklog_module );
150 } else {
151 cfg = (waklog_host_config *)mconfig;
152 }
153
154 cfg->protect = flag;
155 cfg->configured = 1;
156 return( NULL );
157 }
158
159
160 static const char *
161 set_waklog_use_keytab( cmd_parms *params, void *mconfig, char *file )
162 {
163 waklog_host_config *cfg;
164
165 if ( params->path == NULL ) {
166 cfg = (waklog_host_config *) ap_get_module_config(
167 params->server->module_config, &waklog_module );
168 } else {
169 cfg = (waklog_host_config *)mconfig;
170 }
171
172 ap_log_error( APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, params->server,
173 "mod_waklog: using keytab: %s", file );
174
175 cfg->keytab = file;
176 cfg->configured = 1;
177 return( NULL );
178 }
179
180
181 static const char *
182 set_waklog_use_keytab_principal( cmd_parms *params, void *mconfig, char *file )
183 {
184 waklog_host_config *cfg;
185
186 if ( params->path == NULL ) {
187 cfg = (waklog_host_config *) ap_get_module_config(
188 params->server->module_config, &waklog_module );
189 } else {
190 cfg = (waklog_host_config *)mconfig;
191 }
192
193 ap_log_error( APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, params->server,
194 "mod_waklog: using keytab_principal: %s", file );
195
196 cfg->keytab_principal = file;
197 cfg->configured = 1;
198 return( NULL );
199 }
200
201
202 static const char *
203 set_waklog_use_afs_cell( cmd_parms *params, void *mconfig, char *file )
204 {
205 waklog_host_config *cfg;
206
207 if ( params->path == NULL ) {
208 cfg = (waklog_host_config *) ap_get_module_config(
209 params->server->module_config, &waklog_module );
210 } else {
211 cfg = (waklog_host_config *)mconfig;
212 }
213
214 ap_log_error( APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, params->server,
215 "mod_waklog: using afs_cell: %s", file );
216
217 cfg->afs_cell = file;
218 cfg->configured = 1;
219 return( NULL );
220 }
221
222
223 static void
224 waklog_child_init( server_rec *s, pool *p )
225 {
226
227 memset( &child.token, 0, sizeof( struct ktc_token ) );
228
229 setpag();
230
231 return;
232 }
233
234
235 command_rec waklog_cmds[ ] =
236 {
237 { "WaklogProtected", set_waklog_protect,
238 NULL, RSRC_CONF | ACCESS_CONF, FLAG,
239 "enable waklog on a location or directory basis" },
240
241 { "WaklogUseKeytabPath", set_waklog_use_keytab,
242 NULL, RSRC_CONF, TAKE1,
243 "Use the supplied keytab rather than the default" },
244
245 { "WaklogUseKeytabPrincipal", set_waklog_use_keytab_principal,
246 NULL, RSRC_CONF, TAKE1,
247 "Use the supplied keytab principal rather than the default" },
248
249 { "WaklogUseAFSCell", set_waklog_use_afs_cell,
250 NULL, RSRC_CONF, TAKE1,
251 "Use the supplied AFS cell rather than the default" },
252
253 { NULL }
254 };
255
256
257 static void
258 token_cleanup( void *data )
259 {
260 request_rec *r = (request_rec *)data;
261
262 if ( child.token.ticketLen ) {
263 memset( &child.token, 0, sizeof( struct ktc_token ) );
264
265 ktc_ForgetAllTokens();
266
267 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
268 "mod_waklog: ktc_ForgetAllTokens succeeded: pid: %d", getpid() );
269 }
270 return;
271 }
272
273
274 static int
275 waklog_kinit( server_rec *s )
276 {
277 krb5_error_code kerror;
278 krb5_context kcontext = NULL;
279 krb5_principal kprinc = NULL;
280 krb5_get_init_creds_opt kopts;
281 krb5_creds v5creds;
282 krb5_ccache kccache = NULL;
283 krb5_keytab keytab = NULL;
284 char ktbuf[ MAX_KEYTAB_NAME_LEN + 1 ];
285 waklog_host_config *cfg;
286
287 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s,
288 "mod_waklog: waklog_kinit called" );
289
290 cfg = (waklog_host_config *) ap_get_module_config( s->module_config,
291 &waklog_module );
292
293 if (( kerror = krb5_init_context( &kcontext ))) {
294 ap_log_error( APLOG_MARK, APLOG_ERR, s,
295 (char *)error_message( kerror ));
296
297 goto cleanup;
298 }
299
300 /* use the path */
301 if (( kerror = krb5_cc_resolve( kcontext, K5PATH, &kccache )) != 0 ) {
302 ap_log_error( APLOG_MARK, APLOG_ERR, s,
303 (char *)error_message( kerror ));
304
305 goto cleanup;
306 }
307
308 if (( kerror = krb5_parse_name( kcontext, cfg->keytab_principal, &kprinc ))) {
309 ap_log_error( APLOG_MARK, APLOG_ERR, s,
310 (char *)error_message( kerror ));
311
312 goto cleanup;
313 }
314
315 krb5_get_init_creds_opt_init( &kopts );
316 krb5_get_init_creds_opt_set_tkt_life( &kopts, TKT_LIFE );
317 krb5_get_init_creds_opt_set_renew_life( &kopts, 0 );
318 krb5_get_init_creds_opt_set_forwardable( &kopts, 1 );
319 krb5_get_init_creds_opt_set_proxiable( &kopts, 0 );
320
321 /* keytab from config */
322 strncpy( ktbuf, cfg->keytab, sizeof( ktbuf ) - 1 );
323
324 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s,
325 "mod_waklog: waklog_kinit using: %s", ktbuf );
326
327 if (( kerror = krb5_kt_resolve( kcontext, ktbuf, &keytab )) != 0 ) {
328 ap_log_error( APLOG_MARK, APLOG_ERR, s,
329 (char *)error_message( kerror ));
330
331 goto cleanup;
332 }
333
334 /* get the krbtgt */
335 if (( kerror = krb5_get_init_creds_keytab( kcontext, &v5creds,
336 kprinc, keytab, 0, NULL, &kopts ))) {
337
338 ap_log_error( APLOG_MARK, APLOG_ERR, s,
339 (char *)error_message( kerror ));
340
341 goto cleanup;
342 }
343
344 if (( kerror = krb5_verify_init_creds( kcontext, &v5creds,
345 kprinc, keytab, NULL, NULL )) != 0 ) {
346
347 ap_log_error( APLOG_MARK, APLOG_ERR, s,
348 (char *)error_message( kerror ));
349
350 goto cleanup;
351 }
352
353 if (( kerror = krb5_cc_initialize( kcontext, kccache, kprinc )) != 0 ) {
354 ap_log_error( APLOG_MARK, APLOG_ERR, s,
355 (char *)error_message( kerror ));
356
357 goto cleanup;
358 }
359
360 kerror = krb5_cc_store_cred( kcontext, kccache, &v5creds );
361 krb5_free_cred_contents( kcontext, &v5creds );
362 if ( kerror != 0 ) {
363 ap_log_error( APLOG_MARK, APLOG_ERR, s,
364 (char *)error_message( kerror ));
365
366 goto cleanup;
367 }
368
369 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s,
370 "mod_waklog: waklog_kinit success" );
371
372 cleanup:
373 if ( keytab )
374 (void)krb5_kt_close( kcontext, keytab );
375 if ( kprinc )
376 krb5_free_principal( kcontext, kprinc );
377 if ( kccache )
378 krb5_cc_close( kcontext, kccache );
379 if ( kcontext )
380 krb5_free_context( kcontext );
381
382 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s,
383 "mod_waklog: waklog_kinit: exiting" );
384
385 return( 0 );
386 }
387
388
389 static void
390 waklog_aklog( request_rec *r )
391 {
392 int rc;
393 char buf[ 2048 ];
394 const char *k4path = NULL;
395 const char *k5path = NULL;
396 krb5_error_code kerror;
397 krb5_context kcontext = NULL;
398 krb5_creds increds;
399 krb5_creds *v5credsp = NULL;
400 krb5_ccache kccache = NULL;
401 struct ktc_principal server = { "afs", "", "" };
402 struct ktc_principal client;
403 struct ktc_token token;
404 waklog_host_config *cfg;
405 int buflen;
406
407 k5path = ap_table_get( r->subprocess_env, "KRB5CCNAME" );
408 k4path = ap_table_get( r->subprocess_env, "KRBTKFILE" );
409
410 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
411 "mod_waklog: waklog_aklog called: k5path: %s, k4path: %s", k5path, k4path );
412
413 if ( !k5path || !k4path ) {
414 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
415 "mod_waklog: waklog_aklog giving up" );
416 goto cleanup;
417 }
418
419 /*
420 ** Get/build creds from file/tgs, then see if we need to SetToken
421 */
422
423 if (( kerror = krb5_init_context( &kcontext ))) {
424 /* Authentication Required ( kerberos error ) */
425 ap_log_error( APLOG_MARK, APLOG_ERR, r->server,
426 (char *)error_message( kerror ));
427
428 goto cleanup;
429 }
430
431 memset( (char *)&increds, 0, sizeof(increds));
432
433 cfg = (waklog_host_config *) ap_get_module_config(
434 r->server->module_config, &waklog_module );
435
436 /* afs/<cell> or afs */
437 strncpy( buf, "afs", sizeof( buf ) - 1 );
438 if ( strcmp( cfg->afs_cell, AFS_CELL ) ) {
439 strncat( buf, "/" , sizeof( buf ) - strlen( buf ) - 1 );
440 strncat( buf, cfg->afs_cell, sizeof( buf ) - strlen( buf ) - 1 );
441 }
442
443 /* set server part */
444 if (( kerror = krb5_parse_name( kcontext, buf, &increds.server ))) {
445 ap_log_error( APLOG_MARK, APLOG_ERR, r->server,
446 (char *)error_message( kerror ));
447
448 goto cleanup;
449 }
450
451 if (( kerror = krb5_cc_resolve( kcontext, k5path, &kccache )) != 0 ) {
452 ap_log_error( APLOG_MARK, APLOG_ERR, r->server,
453 (char *)error_message( kerror ));
454
455 goto cleanup;
456 }
457
458 /* set client part */
459 krb5_cc_get_principal( kcontext, kccache, &increds.client );
460
461 increds.times.endtime = 0;
462 /* Ask for DES since that is what V4 understands */
463 increds.keyblock.enctype = ENCTYPE_DES_CBC_CRC;
464
465 /* get the V5 credentials */
466 if (( kerror = krb5_get_credentials( kcontext, 0, kccache,
467 &increds, &v5credsp ) ) ) {
468 ap_log_error( APLOG_MARK, APLOG_ERR, r->server,
469 "mod_waklog: krb5_get_credentials: %s", error_message( kerror ));
470 goto cleanup;
471 }
472
473 /* don't overflor */
474 if ( v5credsp->ticket.length >= 344 ) { /* from krb524d.c */
475 ap_log_error( APLOG_MARK, APLOG_ERR, r->server,
476 "mod_waklog: ticket size (%d) to big to fake", v5credsp->ticket.length );
477 goto cleanup;
478 }
479
480 /* assemble the token */
481 memset( &token, 0, sizeof( struct ktc_token ) );
482
483 token.startTime = v5credsp->times.starttime ? v5credsp->times.starttime : v5credsp->times.authtime;
484 token.endTime = v5credsp->times.endtime;
485 memmove( &token.sessionKey, v5credsp->keyblock.contents, v5credsp->keyblock.length );
486 token.kvno = RXKAD_TKT_TYPE_KERBEROS_V5;
487 token.ticketLen = v5credsp->ticket.length;
488 memmove( token.ticket, v5credsp->ticket.data, token.ticketLen );
489
490 /*
491 ** bin_dump( buf, (char *) &token, token.ticketLen + 24 );
492 ** ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
493 ** "mod_waklog: token\n%s", buf );
494 */
495
496 /* make sure we have to do this */
497 if ( child.token.kvno != token.kvno ||
498 child.token.ticketLen != token.ticketLen ||
499 (memcmp( &child.token.sessionKey, &token.sessionKey,
500 sizeof( token.sessionKey ) )) ||
501 (memcmp( child.token.ticket, token.ticket, token.ticketLen )) ) {
502
503 /*
504 ** bin_dump( buf, (char *) &child.token, child.token.ticketLen + 24 );
505 ** ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
506 ** "mod_waklog: child.token\n%s", buf );
507 */
508
509 /* build the name */
510 memmove( buf, v5credsp->client->data[0].data, v5credsp->client->data[0].length );
511 buf[ v5credsp->client->data[0].length ] = '\0';
512 if ( v5credsp->client->length > 1 ) {
513 strncat( buf, ".", sizeof( buf ) - strlen( buf ) - 1 );
514 buflen = strlen( buf );
515 memmove( buf + buflen, v5credsp->client->data[1].data, v5credsp->client->data[1].length );
516 buf[ buflen + v5credsp->client->data[1].length ] = '\0';
517 }
518
519 /* assemble the client */
520 strncpy( client.name, buf, sizeof( client.name ) - 1 );
521 strncpy( client.instance, "", sizeof( client.instance) - 1 );
522 memmove( buf, v5credsp->client->realm.data, v5credsp->client->realm.length );
523 buf[ v5credsp->client->realm.length ] = '\0';
524 strncpy( client.cell, buf, sizeof( client.cell ) - 1 );
525
526 /* assemble the server's cell */
527 strncpy( server.cell, cfg->afs_cell , sizeof( server.cell ) - 1 );
528
529 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
530 "mod_waklog: server: name=%s, instance=%s, cell=%s",
531 server.name, server.instance, server.cell );
532
533 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
534 "mod_waklog: client: name=%s, instance=%s, cell=%s",
535 client.name, client.instance, client.cell );
536
537 /* use the path */
538 krb_set_tkt_string( (char *)k4path );
539
540 /* rumor: we have to do this for AIX 4.1.4 with AFS 3.4+ */
541 write( 2, "", 0 );
542
543 if ( ( rc = ktc_SetToken( &server, &token, &client, 0 ) ) ) {
544 ap_log_error( APLOG_MARK, APLOG_ERR, r->server,
545 "mod_waklog: settoken returned %d", rc );
546 goto cleanup;
547 }
548
549 /* save this */
550 memmove( &child.token, &token, sizeof( struct ktc_token ) );
551
552 /* we'll need to unlog when this connection is done. */
553 ap_register_cleanup( r->pool, (void *)r, token_cleanup, ap_null_cleanup );
554 }
555
556 cleanup:
557 if ( v5credsp )
558 krb5_free_cred_contents( kcontext, v5credsp );
559 if ( increds.client )
560 krb5_free_principal( kcontext, increds.client );
561 if ( increds.server )
562 krb5_free_principal( kcontext, increds.server );
563 if ( kccache )
564 krb5_cc_close( kcontext, kccache );
565 if ( kcontext )
566 krb5_free_context( kcontext );
567
568 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
569 "mod_waklog: finished with waklog_aklog" );
570
571 return;
572
573 }
574
575 static int
576 waklog_child_routine( void *s, child_info *pinfo )
577 {
578 if ( !getuid() ) {
579 ap_log_error( APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, s,
580 "mod_waklog: waklog_child_routine called as root" );
581
582 /* this was causing the credential file to get owned by root */
583 setgid(ap_group_id);
584 setuid(ap_user_id);
585 }
586
587 while( 1 ) {
588 waklog_kinit( s );
589 sleep( SLEEP_TIME );
590 }
591
592 }
593
594
595 static void
596 waklog_init( server_rec *s, pool *p )
597 {
598 extern char *version;
599 int pid;
600
601 ap_log_error( APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, s,
602 "mod_waklog: version %s initialized.", version );
603
604 pid = ap_bspawn_child( p, waklog_child_routine, s, kill_always,
605 NULL, NULL, NULL );
606
607 ap_log_error( APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, s,
608 "mod_waklog: ap_bspawn_child: %d.", pid );
609 }
610
611
612 static int
613 waklog_phase0( request_rec *r )
614 {
615 waklog_host_config *cfg;
616
617 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
618 "mod_waklog: phase0 called" );
619
620 /* directory config? */
621 cfg = (waklog_host_config *)ap_get_module_config(
622 r->per_dir_config, &waklog_module);
623
624 /* server config? */
625 if ( !cfg->configured ) {
626 cfg = (waklog_host_config *)ap_get_module_config(
627 r->server->module_config, &waklog_module);
628 }
629
630 if ( !cfg->protect ) {
631 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
632 "mod_waklog: phase0 declining" );
633 return( DECLINED );
634 }
635
636 /* do this only if we are still unauthenticated */
637 if ( !child.token.ticketLen ) {
638
639 /* set our environment variables */
640 ap_table_set( r->subprocess_env, "KRB5CCNAME", K5PATH );
641 ap_table_set( r->subprocess_env, "KRBTKFILE", K4PATH );
642
643 /* stuff the credentials into the kernel */
644 waklog_aklog( r );
645 }
646
647 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
648 "mod_waklog: phase0 returning" );
649 return DECLINED;
650 }
651
652
653 static int
654 waklog_phase7( request_rec *r )
655 {
656 waklog_host_config *cfg;
657
658 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
659 "mod_waklog: phase7 called" );
660
661 /* directory config? */
662 cfg = (waklog_host_config *)ap_get_module_config(
663 r->per_dir_config, &waklog_module);
664
665 /* server config? */
666 if ( !cfg->configured ) {
667 cfg = (waklog_host_config *)ap_get_module_config(
668 r->server->module_config, &waklog_module);
669 }
670
671 if ( !cfg->protect ) {
672 return( DECLINED );
673 }
674
675 /* stuff the credentials into the kernel */
676 waklog_aklog( r );
677
678 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
679 "mod_waklog: phase7 returning" );
680
681 return DECLINED;
682 }
683
684 static void
685 waklog_new_connection( conn_rec *c ) {
686 ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, c->server,
687 "mod_waklog: new_connection called: pid: %d", getpid() );
688 return;
689 }
690
691 module MODULE_VAR_EXPORT waklog_module = {
692 STANDARD_MODULE_STUFF,
693 waklog_init, /* module initializer */
694 waklog_create_dir_config, /* create per-dir config structures */
695 NULL, /* merge per-dir config structures */
696 waklog_create_server_config, /* create per-server config structures */
697 NULL, /* merge per-server config structures */
698 waklog_cmds, /* table of config file commands */
699 NULL, /* [#8] MIME-typed-dispatched handlers */
700 NULL, /* [#1] URI to filename translation */
701 NULL, /* [#4] validate user id from request */
702 NULL, /* [#5] check if the user is ok _here_ */
703 NULL, /* [#3] check access by host address */
704 NULL, /* [#6] determine MIME type */
705 waklog_phase7, /* [#7] pre-run fixups */
706 NULL, /* [#9] log a transaction */
707 NULL, /* [#2] header parser */
708 waklog_child_init, /* child_init */
709 NULL, /* child_exit */
710 waklog_phase0 /* [#0] post read-request */
711 #ifdef EAPI
712 ,NULL, /* EAPI: add_module */
713 NULL, /* EAPI: remove_module */
714 NULL, /* EAPI: rewrite_command */
715 waklog_new_connection /* EAPI: new_connection */
716 #endif
717 };