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