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