Author: Kev <klmitch@mit.edu>
[ircu2.10.12-pk.git] / ircd / chkconf.c
1 /*
2  * IRC - Internet Relay Chat, ircd/chkconf.c
3  * Copyright (C) 1993 Darren Reed
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 1, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  *
19  * $Id$
20  */
21 #include "config.h"
22
23 #include "s_conf.h"
24 #include "client.h"
25 #include "class.h"
26 #include "fileio.h"
27 #include "ircd.h"
28 #include "ircd_alloc.h"
29 #include "ircd_chattr.h"
30 #include "ircd_string.h"
31 #include "sys.h"
32
33 #include <assert.h>
34 #include <ctype.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43
44 /*
45  * stuff that isn't used by s_conf.c anymore
46  */
47 #define CONF_ME                 0x0040
48 #define CONF_KILL               0x0080
49 #define CONF_ADMIN              0x0100
50 #define CONF_CLASS              0x0400
51 #define CONF_LISTEN_PORT        0x2000
52 #define CONF_IPKILL             0x00010000
53 #define CONF_CRULEALL           0x00200000
54 #define CONF_CRULEAUTO          0x00400000
55 #define CONF_TLINES             0x00800000
56
57 #define CONF_KLINE              (CONF_KILL | CONF_IPKILL)
58 #define CONF_CRULE              (CONF_CRULEALL | CONF_CRULEAUTO)
59
60 /* DEFAULTMAXSENDQLENGTH went into the features subsystem... */
61 #define DEFAULTMAXSENDQLENGTH 40000
62
63 /*
64  * For the connect rule patch..  these really should be in a header,
65  * but i see h.h isn't included for some reason..  so they're here.
66  */
67 struct CRuleNode;
68
69 struct CRuleNode* crule_parse(const char* rule);
70 void crule_free(struct CRuleNode** elem);
71
72 static void new_class(int cn);
73 static char confchar(unsigned int status);
74 static char *getfield(char *newline);
75 static int validate(struct ConfItem *top);
76 static struct ConfItem *chk_initconf(void);
77 static struct ConnectionClass *get_class(int cn, int ism);
78
79 static int numclasses = 0, *classarr = (int *)NULL, debugflag = 0;
80 static char *chk_configfile = "";
81 static char nullfield[] = "";
82 static char maxsendq[12];
83
84 /* A few dummy variables and functions needed to link with runmalloc.o */
85 time_t CurrentTime;
86 struct Client me;
87 void debug(int level, const char *form, ...)
88 {
89   va_list vl;
90   va_start(vl, form);
91   if (debugflag > 8)
92   {
93     vfprintf(stderr, form, vl);
94     fprintf(stderr, "\n");
95   }
96   va_end(vl);
97 }
98 void sendto_one(struct Client *to, char *pattern, ...)
99 {
100 }
101 char *rpl_str(int numeric)
102 {
103   return "";
104 }
105
106 int main(int argc, char *argv[])
107 {
108   const char *dpath = "./";
109   chk_configfile = "ircd.conf";
110
111   while (argc > 1)
112   {
113     if (*argv[1] == '-')
114     {
115       switch (argv[1][1])
116       {
117         case 'd':
118           if (argc > 2)
119           {
120             dpath = argv[2];
121             --argc;
122             ++argv;
123           }
124           else
125           {
126             fprintf(stderr, "-d: Missing path\n");
127             exit(-1);
128           }
129           break;
130         case 'x':
131           debugflag = 1;
132           if (isdigit(argv[1][2]))
133             debugflag = atoi(&argv[1][2]);
134           break;
135         default:
136           fprintf(stderr, "Ignoring unknown option -%c\n", argv[1][1]);
137       }
138     }
139     else
140       chk_configfile = argv[1];
141     --argc;
142     ++argv;
143   }
144
145   if (chdir(dpath))
146   {
147     fprintf(stderr, "chdir(\"%s\") : %s\n", dpath, 
148             (strerror(errno)) ? strerror(errno) : "Unknown error");
149     exit(-1);
150   }
151   else if (debugflag > 1)
152     fprintf(stderr, "chdir(\"%s\") : Success", dpath);
153
154   new_class(0);
155
156   return validate(chk_initconf());
157 }
158
159 /*
160  * chk_initconf()
161  *
162  * Read configuration file.
163  *
164  * Returns -1, if file cannot be opened
165  *          0, if file opened.
166  */
167 static struct ConfItem *chk_initconf(void)
168 {
169   FBFILE *file;
170   char line[512];
171   char *tmp;
172   struct CRuleNode* crule;
173   int ccount = 0, flags = 0;
174   struct ConfItem *aconf = NULL, *ctop = NULL;
175
176   fprintf(stderr, "chk_initconf(): ircd.conf = %s\n", chk_configfile);
177   if (NULL == (file = fbopen(chk_configfile, "r")))
178   {
179     perror("open");
180     return NULL;
181   }
182
183   while (fbgets(line, sizeof(line) - 1, file))
184   {
185     if (aconf) {
186       if (aconf->host)
187         MyFree(aconf->host);
188       if (aconf->passwd)
189         MyFree(aconf->passwd);
190       if (aconf->name)
191         MyFree(aconf->name);
192     }
193     else {
194       aconf = (struct ConfItem*) MyMalloc(sizeof(struct ConfItem));
195       assert(0 != aconf);
196     }
197     aconf->host        = NULL;
198     aconf->passwd      = NULL;
199     aconf->name        = NULL;
200     aconf->conn_class   = NULL;
201     aconf->dns_pending = 0;
202
203     if ((tmp = strchr(line, '\n')))
204       *tmp = 0;
205     /*
206      * Do quoting of characters and # detection.
207      */
208     for (tmp = line; *tmp; ++tmp) {
209       if (*tmp == '\\') {
210         switch (*(tmp + 1)) {
211         case 'n':
212           *tmp = '\n';
213           break;
214         case 'r':
215           *tmp = '\r';
216           break;
217         case 't':
218           *tmp = '\t';
219           break;
220         case '0':
221           *tmp = '\0';
222           break;
223         default:
224           *tmp = *(tmp + 1);
225           break;
226         }
227         if ('\0' == *(tmp + 1))
228           break;
229         else
230           strcpy(tmp + 1, tmp + 2);
231       }
232       else if (*tmp == '#')
233         *tmp = '\0';
234     }
235     if (!*line || *line == '#' || *line == '\n' ||
236         *line == ' ' || *line == '\t')
237       continue;
238
239     if (line[1] != ':')
240     {
241       fprintf(stderr, "ERROR: Bad config line (%s)\n", line);
242       continue;
243     }
244
245     if (debugflag)
246       printf("\n%s\n", line);
247     fflush(stdout);
248
249     tmp = getfield(line);
250     if (!tmp)
251     {
252       fprintf(stderr, "\tERROR: no fields found\n");
253       continue;
254     }
255
256     aconf->status = CONF_ILLEGAL;
257
258     switch (*tmp)
259     {
260       case 'A':         /* Name, e-mail address of administrator */
261       case 'a':         /* of this server. */
262         aconf->status = CONF_ADMIN;
263         break;
264       case 'C':         /* Server where I should try to connect */
265       case 'c':         /* in case of lp failures             */
266         ccount++;
267         aconf->status = CONF_SERVER;
268         break;
269         /* Connect rule */
270       case 'D':
271         aconf->status = CONF_CRULEALL;
272         break;
273         /* Connect rule - autos only */
274       case 'd':
275         aconf->status = CONF_CRULEAUTO;
276         break;
277       case 'H':         /* Hub server line */
278       case 'h':
279         aconf->status = CONF_HUB;
280         break;
281       case 'I':         /* Just plain normal irc client trying  */
282       case 'i':         /* to connect me */
283         aconf->status = CONF_CLIENT;
284         break;
285       case 'K':         /* Kill user line on irc.conf           */
286         aconf->status = CONF_KILL;
287         break;
288       case 'k':         /* Kill user line based on IP in ircd.conf */
289         aconf->status = CONF_IPKILL;
290         break;
291         /* Operator. Line should contain at least */
292         /* password and host where connection is  */
293       case 'L':         /* guaranteed leaf server */
294       case 'l':
295         aconf->status = CONF_LEAF;
296         break;
297         /* Me. Host field is name used for this host */
298         /* and port number is the number of the port */
299       case 'M':
300       case 'm':
301         aconf->status = CONF_ME;
302         break;
303       case 'O':
304         aconf->status = CONF_OPERATOR;
305         break;
306         /* Local Operator, (limited privs --SRB) */
307       case 'o':
308         aconf->status = CONF_LOCOP;
309         break;
310       case 'P':         /* listen port line */
311       case 'p':
312         aconf->status = CONF_LISTEN_PORT;
313         break;
314       case 'T':
315       case 't':
316         aconf->status = CONF_TLINES;
317         break;
318       case 'U':
319       case 'u':
320         aconf->status = CONF_UWORLD;
321         break;
322       case 'Y':
323       case 'y':
324         aconf->status = CONF_CLASS;
325         break;
326       default:
327         fprintf(stderr, "\tERROR: unknown conf line letter (%c)\n", *tmp);
328         break;
329     }
330
331     if (IsIllegal(aconf))
332       continue;
333
334     for (;;)                    /* Fake loop, that I can use break here --msa */
335     {
336       if ((tmp = getfield(NULL)) == NULL)
337         break;
338       DupString(aconf->host, tmp);
339       if ((tmp = getfield(NULL)) == NULL)
340         break;
341       DupString(aconf->passwd, tmp);
342       if ((tmp = getfield(NULL)) == NULL)
343         break;
344       DupString(aconf->name, tmp);
345       if ((tmp = getfield(NULL)) == NULL)
346         break;
347       aconf->port = atoi(tmp);
348       if ((tmp = getfield(NULL)) == NULL)
349         break;
350       if (!(aconf->status & (CONF_CLASS | CONF_ME)))
351       {
352         aconf->conn_class = get_class(atoi(tmp), 0);
353         break;
354       }
355       if (aconf->status & CONF_ME)
356         aconf->conn_class = get_class(atoi(tmp), 1);
357       break;
358     }
359     if (!aconf->conn_class && (aconf->status & (CONF_SERVER |
360         CONF_ME | CONF_OPS | CONF_CLIENT)))
361     {
362       fprintf(stderr, "\tWARNING: No class.      Default 0\n");
363       aconf->conn_class = get_class(0, 0);
364     }
365     /*
366      * If conf line is a class definition, create a class entry
367      * for it and make the conf_line illegal and delete it.
368      */
369     if (aconf->status & CONF_CLASS)
370     {
371       if (!aconf->host)
372       {
373         fprintf(stderr, "\tERROR: no class #\n");
374         continue;
375       }
376       if (!tmp)
377       {
378         fprintf(stderr, "\tWARNING: missing sendq field\n");
379         fprintf(stderr, "\t\t default: %d\n", DEFAULTMAXSENDQLENGTH);
380         sprintf(maxsendq, "%d", DEFAULTMAXSENDQLENGTH);
381       }
382       else
383         sprintf(maxsendq, "%d", atoi(tmp));
384       new_class(atoi(aconf->host));
385       aconf->conn_class = get_class(atoi(aconf->host), 0);
386       goto print_confline;
387     }
388
389     if (aconf->status & CONF_LISTEN_PORT)
390     {
391       if (!aconf->host)
392         fprintf(stderr, "\tERROR: %s\n", "null host field in P-line");
393       else if (strchr(aconf->host, '/'))
394         fprintf(stderr, "\t%s\n", "WARNING: / present in P-line "
395             "for non-UNIXPORT configuration");
396       aconf->conn_class = get_class(0, 0);
397       goto print_confline;
398     }
399
400     if (aconf->status & CONF_SERVER &&
401         (!aconf->host || strchr(aconf->host, '*') || strchr(aconf->host, '?')))
402     {
403       fprintf(stderr, "\tERROR: bad host field\n");
404       continue;
405     }
406
407     if (aconf->status & CONF_SERVER && BadPtr(aconf->passwd))
408     {
409       fprintf(stderr, "\tERROR: empty/no password field\n");
410       continue;
411     }
412
413     if (aconf->status & CONF_SERVER && !aconf->name)
414     {
415       fprintf(stderr, "\tERROR: bad name field\n");
416       continue;
417     }
418
419     if (aconf->status & (CONF_OPS))
420       if (!strchr(aconf->host, '@'))
421       {
422         char *newhost;
423         int len = 3;            /* *@\0 = 3 */
424
425         len += strlen(aconf->host);
426         newhost = (char *)MyMalloc(len);
427         sprintf(newhost, "*@%s", aconf->host);
428         MyFree(aconf->host);
429         aconf->host = newhost;
430       }
431
432     /* parse the connect rules to detect errors, but free
433      *  any allocated storage immediately -- we're just looking
434      *  for errors..  */
435     if (aconf->status & CONF_CRULE)
436       if ((crule = crule_parse(aconf->name)) != NULL)
437         crule_free(&crule);
438
439     if (!aconf->conn_class)
440       aconf->conn_class = get_class(0, 0);
441     sprintf(maxsendq, "%d", ConfClass(aconf));
442
443     if ((aconf->status & CONF_ADMIN) && (!aconf->name ||
444         !aconf->passwd || !aconf->host))
445       fprintf(stderr, "ERROR: Your A: line must have 4 fields!\n");
446
447     if (!aconf->name)
448       DupString(aconf->name, nullfield);
449     if (!aconf->passwd)
450       DupString(aconf->passwd, nullfield);
451     if (!aconf->host)
452       DupString(aconf->host, nullfield);
453     if (aconf->status & (CONF_ME | CONF_ADMIN))
454     {
455       if (flags & aconf->status)
456         fprintf(stderr, "ERROR: multiple %c-lines\n",
457                 ToUpper(confchar(aconf->status)));
458       else
459         flags |= aconf->status;
460     }
461 print_confline:
462     if (debugflag > 8)
463       printf("(%d) (%s) (%s) (%s) (%u) (%s)\n",
464           aconf->status, aconf->host, aconf->passwd,
465           aconf->name, aconf->port, maxsendq);
466     fflush(stdout);
467     if (aconf->status & (CONF_SERVER | CONF_HUB | CONF_LEAF))
468     {
469       aconf->next = ctop;
470       ctop = aconf;
471       aconf = NULL;
472     }
473   }
474   fbclose(file);
475   return ctop;
476 }
477
478 static struct ConnectionClass *get_class(int cn, int ism)
479 {
480   static struct ConnectionClass cls;
481   if (ism == 1)
482   {
483     cls.cc_class = (unsigned int)-1;
484     if ((cn >= 1) && (cn <= 64))
485       cls.cc_class = cn;
486     else
487       fprintf(stderr, "\tWARNING: server numeric %d is not 1-64\n", cn);
488   }
489   else
490   {
491     int i = numclasses - 1;
492     cls.cc_class = (unsigned int)-1;
493     for (; i >= 0; i--)
494       if (classarr[i] == cn)
495       {
496         cls.cc_class = cn;
497         break;
498       }
499     if (i == -1)
500       fprintf(stderr, "\tWARNING: class %d not found\n", cn);
501   }
502   return &cls;
503 }
504
505 static void new_class(int cn)
506 {
507   numclasses++;
508   if (classarr)
509     classarr = (int *)MyRealloc(classarr, sizeof(int) * numclasses);
510   else
511     classarr = (int *)MyMalloc(sizeof(int));
512   classarr[numclasses - 1] = cn;
513 }
514
515 /*
516  * field breakup for ircd.conf file.
517  */
518 static char *getfield(char *newline)
519 {
520   static char *line = NULL;
521   char *end, *field;
522
523   if (newline)
524     line = newline;
525   if (line == NULL)
526     return (NULL);
527
528   field = line;
529   if ((end = strchr(line, ':')) == NULL)
530   {
531     line = NULL;
532     if ((end = strchr(field, '\n')) == NULL)
533       end = field + strlen(field);
534   }
535   else
536     line = end + 1;
537   *end = '\0';
538   return (field);
539 }
540
541 static int validate(struct ConfItem *top)
542 {
543   struct ConfItem *aconf, *bconf;
544   unsigned int otype, valid = 0;
545
546   if (!top)
547     return 0;
548
549   for (aconf = top; aconf; aconf = aconf->next)
550   {
551     if (aconf->status & CONF_MATCH)
552       continue;
553
554     if (aconf->status & CONF_SERVER)
555     {
556       otype = CONF_SERVER;
557
558       for (bconf = top; bconf; bconf = bconf->next)
559       {
560         if (bconf == aconf || !(bconf->status & otype))
561           continue;
562         if (bconf->conn_class == aconf->conn_class &&
563             0 == ircd_strcmp(bconf->name, aconf->name) &&
564             0 == ircd_strcmp(bconf->host, aconf->host))
565         {
566           aconf->status |= CONF_MATCH;
567           bconf->status |= CONF_MATCH;
568           break;
569         }
570       }
571     }
572     else
573       for (bconf = top; bconf; bconf = bconf->next)
574       {
575         if ((bconf == aconf) || !(bconf->status & CONF_SERVER))
576           continue;
577         if (0 == ircd_strcmp(bconf->name, aconf->name))
578         {
579           aconf->status |= CONF_MATCH;
580           break;
581         }
582       }
583   }
584
585   fprintf(stderr, "\n");
586   for (aconf = top; aconf; aconf = aconf->next) {
587     if (aconf->status & CONF_MATCH)
588       valid++;
589     else if ('N' != confchar(aconf->status)) 
590       fprintf(stderr, "Unmatched %c:%s:%s:%s\n",
591           confchar(aconf->status), aconf->host, aconf->passwd, aconf->name);
592   }
593   return valid ? 0 : -1;
594 }
595
596 static char confchar(unsigned int status)
597 {
598   static char letrs[] = "ICNoOMKARYLPH";
599   char *s = letrs;
600
601   status &= ~(CONF_MATCH | CONF_ILLEGAL);
602
603   for (; *s; s++, status >>= 1)
604     if (status & 1)
605       return *s;
606   return '-';
607 }