chiark / gitweb /
Commit 2.4.5-5 as unpacked
[inn-innduct.git] / lib / innconf.c
1 /*  $Id: innconf.c 7751 2008-04-06 14:35:40Z iulius $
2 **
3 **  Manage the global innconf struct.
4 **
5 **  The functions in this file collapse the parse tree for inn.conf into the
6 **  innconf struct that's used throughout INN.  The code to collapse a
7 **  configuration parse tree into a struct is fairly generic and should
8 **  probably be moved into a separate library.
9 **
10 **  When adding new inn.conf parameters, make sure to add them in all of the
11 **  following places:
12 **
13 **   * The table in this file.
14 **   * include/inn/innconf.h
15 **   * doc/pod/inn.conf.pod (and regenerate doc/man/inn.conf.5)
16 **   * Add the default value to samples/inn.conf.in
17 **
18 **  Please maintain the current organization of parameters.  There are two
19 **  different orders, one of which is a logical order used by the
20 **  documentation, the include file, and the sample file, and the other of
21 **  which is used in this file.  The order in this file is documentation of
22 **  where each parameter is used, for later work at breaking up this mess
23 **  of parameters into separate configuration groups for each INN subsystem.
24 */
25
26 #include "config.h"
27 #include "clibrary.h"
28 #include <ctype.h>
29
30 #include "inn/confparse.h"
31 #include "inn/innconf.h"
32 #include "inn/messages.h"
33 #include "inn/vector.h"
34 #include "libinn.h"
35 #include "paths.h"
36
37 /* Instantiation of the global innconf variable. */
38 struct innconf *innconf = NULL;
39
40 /* Data types used to express the mappings from the configuration parse into
41    the innconf struct. */
42
43 enum type {
44     TYPE_BOOLEAN,
45     TYPE_NUMBER,
46     TYPE_STRING
47 };
48
49 struct config {
50     const char *name;
51     size_t location;
52     enum type type;
53     struct {
54         bool boolean;
55         long integer;
56         const char *string;
57     } defaults;
58 };
59
60 /* The following macros are helpers to make it easier to define the table that
61    specifies how to convert the configuration file into a struct. */
62
63 #define K(name)         (#name), offsetof(struct innconf, name)
64
65 #define BOOL(def)       TYPE_BOOLEAN, { (def),     0,  NULL }
66 #define NUMBER(def)     TYPE_NUMBER,  {     0, (def),  NULL }
67 #define STRING(def)     TYPE_STRING,  {     0,     0, (def) }
68
69 /* Accessor macros to get a pointer to a value inside a struct. */
70 #define CONF_BOOL(conf, offset)   (bool *) (void *)((char *) (conf) + (offset))
71 #define CONF_LONG(conf, offset)   (long *) (void *)((char *) (conf) + (offset))
72 #define CONF_STRING(conf, offset) (char **)(void *)((char *) (conf) + (offset))
73
74 /* Special notes:
75
76    checkincludedtext and localmaxartisize are used by both nnrpd and inews,
77    but inews should probably just let nnrpd do that checking.
78
79    organization is used by both nnrpd and inews.  Perhaps inews should just
80    let nnrpd set it.
81
82    mergetogroups is currently used by nnrpd for permission checking on
83    posting.  I think the check should always be performed based on the
84    newsgroup to which the user is actually posting, and nnrpd should let
85    innd do the merging.
86
87    useoverchan is only used in innd and overchan.  It should probably be
88    something the storage system knows.  Ideally, the storage system would
89    handle overchan itself, but that would require a lot of infrastructure;
90    in the interim, it could be something that programs could ask the
91    overview subsystem about.
92
93    doinnwatch and docnfsstat are used by rc.news currently, but really
94    should be grouped with the appropriate subsystem.
95
96    newsrequeue also uses nntplinklog, but the parameter should go away
97    completely anyway.
98
99    timer is currently used in various places, but it may be best to replace
100    it with individual settings for innd and innfeed, and any other code that
101    wants to use it.
102
103    maxforks is used by rnews and nnrpd.  I'm not sure this is that useful of
104    a setting to have.
105 */
106
107 const struct config config_table[] = {
108     { K(domain),                STRING  (NULL) },
109     { K(enableoverview),        BOOL    (true) },
110     { K(fromhost),              STRING  (NULL) },
111     { K(groupbaseexpiry),       BOOL    (true) },
112     { K(mailcmd),               STRING  (NULL) },
113     { K(maxforks),              NUMBER  (10) },
114     { K(mta),                   STRING  (NULL) },
115     { K(nicekids),              NUMBER  (4) },
116     { K(ovmethod),              STRING  (NULL) },
117     { K(pathhost),              STRING  (NULL) },
118     { K(rlimitnofile),          NUMBER  (-1) },
119     { K(server),                STRING  (NULL) },
120     { K(sourceaddress),         STRING  (NULL) },
121     { K(sourceaddress6),        STRING  (NULL) },
122     { K(timer),                 NUMBER  (0) },
123
124     { K(patharchive),           STRING  (NULL) },
125     { K(patharticles),          STRING  (NULL) },
126     { K(pathbin),               STRING  (NULL) },
127     { K(pathcontrol),           STRING  (NULL) },
128     { K(pathdb),                STRING  (NULL) },
129     { K(pathetc),               STRING  (NULL) },
130     { K(pathfilter),            STRING  (NULL) },
131     { K(pathhttp),              STRING  (NULL) },
132     { K(pathincoming),          STRING  (NULL) },
133     { K(pathlog),               STRING  (NULL) },
134     { K(pathnews),              STRING  (NULL) },
135     { K(pathoutgoing),          STRING  (NULL) },
136     { K(pathoverview),          STRING  (NULL) },
137     { K(pathrun),               STRING  (NULL) },
138     { K(pathspool),             STRING  (NULL) },
139     { K(pathtmp),               STRING  (NULL) },
140
141     /* The following settings are specific to innd. */
142     { K(artcutoff),             NUMBER  (10) },
143     { K(badiocount),            NUMBER  (5) },
144     { K(bindaddress),           STRING  (NULL) },
145     { K(bindaddress6),          STRING  (NULL) },
146     { K(blockbackoff),          NUMBER  (120) },
147     { K(chaninacttime),         NUMBER  (600) },
148     { K(chanretrytime),         NUMBER  (300) },
149     { K(datamovethreshold),     NUMBER  (8192) },
150     { K(dontrejectfiltered),    BOOL    (false) },
151     { K(hiscachesize),          NUMBER  (0) },
152     { K(icdsynccount),          NUMBER  (10) },
153     { K(ignorenewsgroups),      BOOL    (false) },
154     { K(linecountfuzz),         NUMBER  (0) },
155     { K(logartsize),            BOOL    (true) },
156     { K(logcancelcomm),         BOOL    (false) },
157     { K(logipaddr),             BOOL    (true) },
158     { K(logsitename),           BOOL    (true) },
159     { K(maxartsize),            NUMBER  (1000000) },
160     { K(maxconnections),        NUMBER  (50) },
161     { K(mergetogroups),         BOOL    (false) },
162     { K(nntpactsync),           NUMBER  (200) },
163     { K(nntplinklog),           BOOL    (false) },
164     { K(noreader),              BOOL    (false) },
165     { K(pathalias),             STRING  (NULL) },
166     { K(pathcluster),           STRING  (NULL) },
167     { K(pauseretrytime),        NUMBER  (300) },
168     { K(peertimeout),           NUMBER  (3600) },
169     { K(port),                  NUMBER  (119) },
170     { K(readerswhenstopped),    BOOL    (false) },
171     { K(refusecybercancels),    BOOL    (false) },
172     { K(remembertrash),         BOOL    (true) },
173     { K(stathist),              STRING  (NULL) },
174     { K(status),                NUMBER  (0) },
175     { K(verifycancels),         BOOL    (false) },
176     { K(wanttrash),             BOOL    (false) },
177     { K(wipcheck),              NUMBER  (5) },
178     { K(wipexpire),             NUMBER  (10) },
179     { K(xrefslave),             BOOL    (false) },
180
181     /* The following settings are specific to nnrpd. */
182     { K(addnntppostingdate),    BOOL    (true) },
183     { K(addnntppostinghost),    BOOL    (true) },
184     { K(allownewnews),          BOOL    (true) },
185     { K(backoffauth),           BOOL    (false) },
186     { K(backoffdb),             STRING  (NULL) },
187     { K(backoffk),              NUMBER  (1) },
188     { K(backoffpostfast),       NUMBER  (0) },
189     { K(backoffpostslow),       NUMBER  (1) },
190     { K(backofftrigger),        NUMBER  (10000) },
191     { K(checkincludedtext),     BOOL    (false) },
192     { K(clienttimeout),         NUMBER  (600) },
193     { K(complaints),            STRING  (NULL) },
194     { K(initialtimeout),        NUMBER  (10) },
195     { K(keyartlimit),           NUMBER  (100000) },
196     { K(keylimit),              NUMBER  (512) },
197     { K(keymaxwords),           NUMBER  (250) },
198     { K(keywords),              BOOL    (false) },
199     { K(localmaxartsize),       NUMBER  (1000000) },
200     { K(maxcmdreadsize),        NUMBER  (BUFSIZ) },
201     { K(msgidcachesize),        NUMBER  (10000) },
202     { K(moderatormailer),       STRING  (NULL) },
203     { K(nfsreader),             BOOL    (false) },
204     { K(nfsreaderdelay),        NUMBER  (60) },
205     { K(nicenewnews),           NUMBER  (0) },
206     { K(nicennrpd),             NUMBER  (0) },
207     { K(nnrpdflags),            STRING  ("") },
208     { K(nnrpdauthsender),       BOOL    (false) },
209     { K(nnrpdloadlimit),        NUMBER  (16) },
210     { K(nnrpdoverstats),        BOOL    (false) },
211     { K(organization),          STRING  (NULL) },
212     { K(readertrack),           BOOL    (false) },
213     { K(spoolfirst),            BOOL    (false) },
214     { K(strippostcc),           BOOL    (false) },
215
216     /* The following settings are used by nnrpd and rnews. */
217     { K(nnrpdposthost),         STRING  (NULL) },
218     { K(nnrpdpostport),         NUMBER  (119) },
219
220     /* The following settings are specific to the storage subsystem. */
221     { K(articlemmap),           BOOL    (false) },
222     { K(cnfscheckfudgesize),    NUMBER  (0) },
223     { K(immediatecancel),       BOOL    (false) },
224     { K(keepmmappedthreshold),  NUMBER  (1024) },
225     { K(nfswriter),             BOOL    (false) },
226     { K(nnrpdcheckart),         BOOL    (true) },
227     { K(overcachesize),         NUMBER  (15) },
228     { K(ovgrouppat),            STRING  (NULL) },
229     { K(storeonxref),           BOOL    (true) },
230     { K(tradindexedmmap),       BOOL    (true) },
231     { K(useoverchan),           BOOL    (false) },
232     { K(wireformat),            BOOL    (false) },
233
234     /* The following settings are specific to the history subsystem. */
235     { K(hismethod),             STRING  (NULL) },
236
237     /* The following settings are specific to rc.news. */
238     { K(docnfsstat),            BOOL    (false) },
239     { K(innflags),              STRING  (NULL) },
240     { K(pgpverify),             BOOL    (false) },
241
242     /* The following settings are specific to innwatch. */
243     { K(doinnwatch),            BOOL    (true) },
244     { K(innwatchbatchspace),    NUMBER  (800) },
245     { K(innwatchlibspace),      NUMBER  (25000) },
246     { K(innwatchloload),        NUMBER  (1000) },
247     { K(innwatchhiload),        NUMBER  (2000) },
248     { K(innwatchpauseload),     NUMBER  (1500) },
249     { K(innwatchsleeptime),     NUMBER  (600) },
250     { K(innwatchspoolnodes),    NUMBER  (200) },
251     { K(innwatchspoolspace),    NUMBER  (8000) },
252
253     /* The following settings are specific to scanlogs. */
254     { K(logcycles),             NUMBER  (3) },
255 };
256
257
258 /*
259 **  Set some defaults that cannot be included in the table because they depend
260 **  on other elements or require function calls to set.  Called after the
261 **  configuration file is read, so any that have to override what's read have
262 **  to free whatever values are set by the file.
263 */
264 static void
265 innconf_set_defaults(void)
266 {
267     char *value;
268
269     /* Some environment variables override settings in inn.conf. */
270     value = getenv("FROMHOST");
271     if (value != NULL) {
272         if (innconf->fromhost != NULL)
273             free(innconf->fromhost);
274         innconf->fromhost = xstrdup(value);
275     }
276     value = getenv("NNTPSERVER");
277     if (value != NULL) {
278         if (innconf->server != NULL)
279             free(innconf->server);
280         innconf->server = xstrdup(value);
281     }
282     value = getenv("ORGANIZATION");
283     if (value != NULL) {
284         if (innconf->organization != NULL)
285             free(innconf->organization);
286         innconf->organization = xstrdup(value);
287     }
288     value = getenv("INND_BIND_ADDRESS");
289     if (value != NULL) {
290         if (innconf->bindaddress != NULL)
291             free(innconf->bindaddress);
292         innconf->bindaddress = xstrdup(value);
293     }
294     value = getenv("INND_BIND_ADDRESS6");
295     if (value != NULL) {
296         if (innconf->bindaddress6 != NULL)
297             free(innconf->bindaddress6);
298         innconf->bindaddress6 = xstrdup(value);
299     }
300
301     /* Some parameters have defaults that depend on other parameters. */
302     if (innconf->fromhost == NULL)
303         innconf->fromhost = xstrdup(GetFQDN(innconf->domain));
304     if (innconf->pathhost == NULL)
305         innconf->pathhost = xstrdup(GetFQDN(innconf->domain));
306     if (innconf->pathtmp == NULL)
307         innconf->pathtmp = xstrdup(_PATH_TMP);
308
309     /* All of the paths are relative to other paths if not set except for
310        pathnews, which is required to be set by innconf_validate. */
311     if (innconf->pathbin == NULL)
312         innconf->pathbin = concatpath(innconf->pathnews, "bin");
313     if (innconf->pathfilter == NULL)
314         innconf->pathfilter = concatpath(innconf->pathbin, "filter");
315     if (innconf->pathdb == NULL)
316         innconf->pathdb = concatpath(innconf->pathnews, "db");
317     if (innconf->pathetc == NULL)
318         innconf->pathetc = concatpath(innconf->pathnews, "etc");
319     if (innconf->pathrun == NULL)
320         innconf->pathrun = concatpath(innconf->pathnews, "run");
321     if (innconf->pathlog == NULL)
322         innconf->pathlog = concatpath(innconf->pathnews, "log");
323     if (innconf->pathhttp == NULL)
324         innconf->pathhttp = xstrdup(innconf->pathlog);
325     if (innconf->pathspool == NULL)
326         innconf->pathspool = concatpath(innconf->pathnews, "spool");
327     if (innconf->patharticles == NULL)
328         innconf->patharticles = concatpath(innconf->pathspool, "articles");
329     if (innconf->pathoverview == NULL)
330         innconf->pathoverview = concatpath(innconf->pathspool, "overview");
331     if (innconf->pathoutgoing == NULL)
332         innconf->pathoutgoing = concatpath(innconf->pathspool, "outgoing");
333     if (innconf->pathincoming == NULL)
334         innconf->pathincoming = concatpath(innconf->pathspool, "incoming");
335     if (innconf->patharchive == NULL)
336         innconf->patharchive = concatpath(innconf->pathspool, "archive");
337
338     /* One other parameter depends on pathbin. */
339     if (innconf->mailcmd == NULL)
340         innconf->mailcmd = concatpath(innconf->pathbin, "innmail");
341 }
342
343
344 /*
345 **  Given a config_group struct representing the inn.conf file, parse that
346 **  into an innconf struct and return the newly allocated struct.  This
347 **  routine should be pulled out into a library for smashing configuration
348 **  file parse results into structs.
349 */
350 static struct innconf *
351 innconf_parse(struct config_group *group)
352 {
353     unsigned int i;
354     bool *bool_ptr;
355     long *long_ptr;
356     const char *char_ptr;
357     char **string;
358     struct innconf *config;
359
360     config = xmalloc(sizeof(struct innconf));
361     for (i = 0; i < ARRAY_SIZE(config_table); i++)
362         switch (config_table[i].type) {
363         case TYPE_BOOLEAN:
364             bool_ptr = CONF_BOOL(config, config_table[i].location);
365             if (!config_param_boolean(group, config_table[i].name, bool_ptr))
366                 *bool_ptr = config_table[i].defaults.boolean;
367             break;
368         case TYPE_NUMBER:
369             long_ptr = CONF_LONG(config, config_table[i].location);
370             if (!config_param_integer(group, config_table[i].name, long_ptr))
371                 *long_ptr = config_table[i].defaults.integer;
372             break;
373         case TYPE_STRING:
374             if (!config_param_string(group, config_table[i].name, &char_ptr))
375                 char_ptr = config_table[i].defaults.string;
376             string = CONF_STRING(config, config_table[i].location);
377             *string = (char_ptr == NULL) ? NULL : xstrdup(char_ptr);
378             break;
379         default:
380             die("internal error: invalid type in row %u of config table", i);
381             break;
382         }
383     return config;
384 }
385
386
387 /*
388 **  Check the configuration file for consistency and ensure that mandatory
389 **  settings are present.  Returns true if the file is okay and false
390 **  otherwise.
391 */
392 static bool
393 innconf_validate(struct config_group *group)
394 {
395     bool okay = true;
396     long threshold;
397
398     if (GetFQDN(innconf->domain) == NULL) {
399         warn("hostname does not resolve or domain not set in inn.conf");
400         okay = false;
401     }
402     if (innconf->mta == NULL) {
403         warn("must set mta in inn.conf");
404         okay = false;
405     }
406     if (innconf->pathnews == NULL) {
407         warn("must set pathnews in inn.conf");
408         okay = false;
409     }
410     if (innconf->hismethod == NULL) {
411         warn("must set hismethod in inn.conf");
412         okay = false;
413     }
414     if (innconf->enableoverview && innconf->ovmethod == NULL) {
415         warn("ovmethod must be set in inn.conf if enableoverview is true");
416         okay = false;
417     }
418     threshold = innconf->datamovethreshold;
419     if (threshold <= 0 || threshold > 1024 * 1024) {
420         config_error_param(group, "datamovethreshold",
421                            "maximum value for datamovethreshold is 1MB");
422         innconf->datamovethreshold = 1024 * 1024;
423     }
424     return okay;
425 }
426
427
428 /*
429 **  Read in inn.conf.  Takes a single argument, which is either NULL to read
430 **  the default configuration file or a path to an alternate configuration
431 **  file to read.  Returns true if the file was read successfully and false
432 **  otherwise.
433 */
434 bool
435 innconf_read(const char *path)
436 {
437     struct config_group *group;
438     char *tmpdir;
439
440     if (innconf != NULL)
441         innconf_free(innconf);
442     if (path == NULL)
443         path = getenv("INNCONF");
444     group = config_parse_file(path == NULL ? _PATH_CONFIG : path);
445     if (group == NULL)
446         return false;
447
448     innconf = innconf_parse(group);
449     if (!innconf_validate(group))
450         return false;
451     config_free(group);
452     innconf_set_defaults();
453
454     /* It's not clear that this belongs here, but it was done by the old
455        configuration parser, so this is a convenient place to do it. */
456     tmpdir = getenv("TMPDIR");
457     if (tmpdir == NULL || strcmp(tmpdir, innconf->pathtmp) != 0)
458         if (setenv("TMPDIR", innconf->pathtmp, true) != 0) {
459             warn("cannot set TMPDIR in the environment");
460             return false;
461         }
462
463     return true;
464 }
465
466
467 /*
468 **  Check an inn.conf file.  This involves reading it in and then additionally
469 **  making sure that there are no keys defined in the inn.conf file that
470 **  aren't recognized.  This doesn't have to be very fast (and isn't).
471 **  Returns true if everything checks out successfully, and false otherwise.
472 **
473 **  A lot of code is duplicated with innconf_read here and should be
474 **  refactored.
475 */
476 bool
477 innconf_check(const char *path)
478 {
479     struct config_group *group;
480     struct vector *params;
481     size_t set, known;
482     bool found;
483     bool okay = true;
484
485     if (innconf != NULL)
486         innconf_free(innconf);
487     if (path == NULL)
488         path = getenv("INNCONF");
489     group = config_parse_file(path == NULL ? _PATH_CONFIG : path);
490     if (group == NULL)
491         return false;
492
493     innconf = innconf_parse(group);
494     if (!innconf_validate(group))
495         return false;
496
497     /* Now, do the work that innconf_read doesn't do.  Get a list of
498        parameters defined in innconf and then walk our list of valid
499        parameters and see if there are any set that we don't recognize. */
500     params = config_params(group);
501     for (set = 0; set < params->count; set++) {
502         found = false;
503         for (known = 0; known < ARRAY_SIZE(config_table); known++)
504             if (strcmp(params->strings[set], config_table[known].name) == 0)
505                 found = true;
506         if (!found) {
507             config_error_param(group, params->strings[set],
508                                "unknown parameter %s", params->strings[set]);
509             okay = false;
510         }
511     }
512
513     /* Check and warn about a few other parameters. */
514     if (innconf->peertimeout < 3 * 60)
515         config_error_param(group, "peertimeout",
516                            "warning: NNTP draft (15) states inactivity"
517                            " timeouts MUST be at least three minutes");
518     if (innconf->clienttimeout < 3 * 60)
519         config_error_param(group, "clienttimeout",
520                            "warning: NNTP draft (15) states inactivity"
521                            " timeouts MUST be at least three minutes");
522
523     /* All done.  Free the parse tree and return. */
524     config_free(group);
525     return okay;
526 }
527
528
529 /*
530 **  Free innconf, requiring some complexity since all strings stored in the
531 **  innconf struct are allocated memory.  This routine is mostly generic to
532 **  any struct smashed down from a configuration file parse.
533 */
534 void
535 innconf_free(struct innconf *config)
536 {
537     unsigned int i;
538     char *p;
539
540     for (i = 0; i < ARRAY_SIZE(config_table); i++)
541         if (config_table[i].type == TYPE_STRING) {
542             p = *CONF_STRING(config, config_table[i].location);
543             if (p != NULL)
544                 free(p);
545         }
546     free(config);
547 }
548
549
550 /*
551 **  Print a single boolean value with appropriate quoting.
552 */
553 static void
554 print_boolean(FILE *file, const char *key, bool value,
555               enum innconf_quoting quoting)
556 {
557     char *upper, *p;
558
559     switch (quoting) {
560     case INNCONF_QUOTE_NONE:
561         fprintf(file, "%s\n", value ? "true" : "false");
562         break;
563     case INNCONF_QUOTE_SHELL:
564         upper = xstrdup(key);
565         for (p = upper; *p != '\0'; p++)
566             *p = toupper(*p);
567         fprintf(file, "%s=%s; export %s;\n", upper, value ? "true" : "false",
568                 upper);
569         free(upper);
570         break;
571     case INNCONF_QUOTE_PERL:
572         fprintf(file, "$%s = '%s';\n", key, value ? "true" : "false");
573         break;
574     case INNCONF_QUOTE_TCL:
575         fprintf(file, "set inn_%s \"%s\"\n", key, value ? "true" : "false");
576         break;
577     }
578 }
579
580
581 /*
582 **  Print a single integer value with appropriate quoting.
583 */
584 static void
585 print_number(FILE *file, const char *key, long value,
586              enum innconf_quoting quoting)
587 {
588     char *upper, *p;
589
590     switch (quoting) {
591     case INNCONF_QUOTE_NONE:
592         fprintf(file, "%ld\n", value);
593         break;
594     case INNCONF_QUOTE_SHELL:
595         upper = xstrdup(key);
596         for (p = upper; *p != '\0'; p++)
597             *p = toupper(*p);
598         fprintf(file, "%s=%ld; export %s;\n", upper, value, upper);
599         free(upper);
600         break;
601     case INNCONF_QUOTE_PERL:
602         fprintf(file, "$%s = %ld;\n", key, value);
603         break;
604     case INNCONF_QUOTE_TCL:
605         fprintf(file, "set inn_%s %ld\n", key, value);
606         break;
607     }
608 }
609
610
611 /*
612 **  Print a single string value with appropriate quoting.
613 */
614 static void
615 print_string(FILE *file, const char *key, const char *value,
616              enum innconf_quoting quoting)
617 {
618     char *upper, *p;
619     const char *letter;
620     static const char tcl_unsafe[] = "$[]{}\"\\";
621
622     switch (quoting) {
623     case INNCONF_QUOTE_NONE:
624         fprintf(file, "%s\n", value != NULL ? value : "");
625         break;
626     case INNCONF_QUOTE_SHELL:
627         upper = xstrdup(key);
628         for (p = upper; *p != '\0'; p++)
629             *p = toupper(*p);
630         fprintf(file, "%s='", upper);
631         for (letter = value; letter != NULL && *letter != '\0'; letter++) {
632             if (*letter == '\'')
633                 fputs("'\\''", file);
634             else if (*letter == '\\')
635                 fputs("\\\\", file);
636             else
637                 fputc(*letter, file);
638         }
639         fprintf(file, "'; export %s;\n", upper);
640         free(upper);
641         break;
642     case INNCONF_QUOTE_PERL:
643         fprintf(file, "$%s = '", key);
644         for (letter = value; letter != NULL && *letter != '\0'; letter++) {
645             if (*letter == '\'' || *letter == '\\')
646                 fputc('\\', file);
647             fputc(*letter, file);
648         }
649         fputs("';\n", file);
650         break;
651     case INNCONF_QUOTE_TCL:
652         fprintf(file, "set inn_%s \"", key);
653         for (letter = value; letter != NULL && *letter != '\0'; letter++) {
654             if (strchr(tcl_unsafe, *letter) != NULL)
655                 fputc('\\', file);
656             fputc(*letter, file);
657         }
658         fputs("\"\n", file);
659         break;
660     }
661 }
662
663
664 /*
665 **  Print a single paramter to the given file.  Takes an index into the table
666 **  specifying the attribute to print and the quoting.
667 */
668 static void
669 print_parameter(FILE *file, size_t i, enum innconf_quoting quoting)
670 {
671     bool bool_val;
672     long long_val;
673     const char *string_val;
674
675     switch (config_table[i].type) {
676     case TYPE_BOOLEAN:
677         bool_val = *CONF_BOOL(innconf, config_table[i].location);
678         print_boolean(file, config_table[i].name, bool_val, quoting);
679         break;
680     case TYPE_NUMBER:
681         long_val = *CONF_LONG(innconf, config_table[i].location);
682         print_number(file, config_table[i].name, long_val, quoting);
683         break;
684     case TYPE_STRING:
685         string_val = *CONF_STRING(innconf, config_table[i].location);
686         print_string(file, config_table[i].name, string_val, quoting);
687         break;
688     default:
689         die("internal error: invalid type in row %d of config table", i);
690         break;
691     }
692 }
693
694
695 /*
696 **  Given a single parameter, find it in the table and print out its value.
697 */
698 bool
699 innconf_print_value(FILE *file, const char *key, enum innconf_quoting quoting)
700 {
701     size_t i;
702
703     for (i = 0; i < ARRAY_SIZE(config_table); i++)
704         if (strcmp(key, config_table[i].name) == 0) {
705             print_parameter(file, i, quoting);
706             return true;
707         }
708     return false;
709 }
710
711
712 /*
713 **  Dump the entire inn.conf configuration with appropriate quoting.
714 */
715 void
716 innconf_dump(FILE *file, enum innconf_quoting quoting)
717 {
718     size_t i;
719
720     for (i = 0; i < ARRAY_SIZE(config_table); i++)
721         print_parameter(file, i, quoting);
722 }
723
724
725 /*
726 **  Compare two innconf structs to see if they represent identical
727 **  configurations.  This routine is mostly used for testing.  Prints warnings
728 **  about where the two configurations differ and return false if they differ,
729 **  true if they match.  This too should be moved into a config smashing
730 **  library.
731 */
732 bool
733 innconf_compare(struct innconf *conf1, struct innconf *conf2)
734 {
735     unsigned int i;
736     bool bool1, bool2;
737     long long1, long2;
738     const char *string1, *string2;
739     bool okay = true;
740
741     for (i = 0; i < ARRAY_SIZE(config_table); i++)
742         switch (config_table[i].type) {
743         case TYPE_BOOLEAN:
744             bool1 = *CONF_BOOL(conf1, config_table[i].location);
745             bool2 = *CONF_BOOL(conf2, config_table[i].location);
746             if (bool1 != bool2) {
747                 warn("boolean variable %s differs: %d != %d",
748                      config_table[i].name, bool1, bool2);
749                 okay = false;
750             }
751             break;
752         case TYPE_NUMBER:
753             long1 = *CONF_LONG(conf1, config_table[i].location);
754             long2 = *CONF_LONG(conf2, config_table[i].location);
755             if (long1 != long2) {
756                 warn("integer variable %s differs: %ld != %ld",
757                      config_table[i].name, long1, long2);
758                 okay = false;
759             }
760             break;
761         case TYPE_STRING:
762             string1 = *CONF_STRING(conf1, config_table[i].location);
763             string2 = *CONF_STRING(conf2, config_table[i].location);
764             if (string1 == NULL && string2 != NULL) {
765                 warn("string variable %s differs: NULL != %s",
766                      config_table[i].name, string2);
767                 okay = false;
768             } else if (string1 != NULL && string2 == NULL) {
769                 warn("string variable %s differs: %s != NULL",
770                      config_table[i].name, string1);
771                 okay = false;
772             } else if (string1 != NULL && string2 != NULL) {
773                 if (strcmp(string1, string2) != 0) {
774                     warn("string variable %s differs: %s != %s",
775                          config_table[i].name, string1, string2);
776                     okay = false;
777                 }
778             }
779             break;
780         default:
781             die("internal error: invalid type in row %d of config table", i);
782             break;
783         }
784     return okay;
785 }