/************************************************************************ * IRC - Internet Relay Chat, ircd/chkconf.c * Copyright (C) 1993 Darren Reed * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 1, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef lint static char rcsid[] = "@(#)$Id: chkconf.c,v 1.7 2003/10/28 00:18:31 gvs Exp $"; #endif #include "os.h" #include "s_defines.h" #define CHKCONF_C #include "match_ext.h" #undef CHKCONF_C #define MyMalloc(x) malloc(x) /*#define MyFree(x) free(x)*/ static void new_class(); static char *getfield(), confchar (); static int openconf(char *), validate __P((aConfItem *)); static aClass *get_class(); static aConfItem *initconf(); static int dgets(int, char *, int, char **, char **); static int numclasses = 0, *classarr = (int *)NULL, debugflag = 0; static char *configfile = IRCDCONF_PATH; static char nullfield[] = ""; static char maxsendq[12]; #define SHOWSTR(x) ((x) ? (x) : "*") int main(argc, argv) int argc; char *argv[]; { if (argc > 1 && !strncmp(argv[1], "-h", 2)) { (void)fprintf(stderr, "Usage: %s [-h] [-d[#]] [%s]\n", argv[0], IRCDCONF_PATH); exit(0); } new_class(0); if (argc > 1 && !strncmp(argv[1], "-d", 2)) { debugflag = 1; if (argv[1][2]) debugflag = atoi(argv[1]+2); argc--, argv++; } if (argc > 1) configfile = argv[1]; return validate(initconf(configfile)); } /* * openconf * * returns -1 on any error or else the fd opened from which to read the * configuration file from. This may either be the file direct or one end * of a pipe from m4. */ int openconf(conf_file) char *conf_file; { #ifdef M4_PREPROC int pi[2], i; if (pipe(pi) == -1) return -1; switch(vfork()) { case -1 : return -1; case 0 : (void)close(pi[0]); if (pi[1] != 1) { (void)dup2(pi[1], 1); (void)close(pi[1]); } (void)dup2(1,2); /* * m4 maybe anywhere, use execvp to find it. Any error * goes out with report_error. Could be dangerous, * two servers running with the same fd's >:-) -avalon */ (void)execlp("m4", "m4", IRCDM4_PATH, configfile, 0); perror("m4"); exit(-1); default : (void)close(pi[1]); return pi[0]; } #else return open(conf_file, O_RDONLY); #endif } /* ** initconf() ** Read configuration file. ** ** returns -1, if file cannot be opened ** 0, if file opened */ static aConfItem *initconf(conf_file) char *conf_file; { int fd; char line[4096], *head, *tail, *tmp, *s; int ccount = 0, ncount = 0, dh, flags = 0; aConfItem *aconf = NULL, *ctop = NULL; int mandatory_found = 0, valid = 1; (void)fprintf(stderr, "initconf(): ircd.conf = %s\n", configfile); if ((fd = openconf(conf_file)) == -1) { #ifdef M4_PREPROC (void)wait(0); #endif return NULL; } (void)dgets(-1, line, 0, &head, &tail); /* initialize line */ while ((dh = dgets(fd, line, sizeof(line) - 1, &head, &tail)) > 0) { if (aconf) { if (aconf->host && (aconf->host != nullfield)) (void)free(aconf->host); if (aconf->passwd && (aconf->passwd != nullfield)) (void)free(aconf->passwd); if (aconf->name && (aconf->name != nullfield)) (void)free(aconf->name); } else aconf = (aConfItem *)malloc(sizeof(*aconf)); aconf->host = (char *)NULL; aconf->passwd = (char *)NULL; aconf->name = (char *)NULL; aconf->class = (aClass *)NULL; if ((tmp = (char *)index(line, '\n'))) *tmp = 0; #ifdef RUSNET_IRCD if (*line == '#') /* handle inclusions */ { if (!strncasecmp(line + 1, "include", 7)) { s = strtok(line + 8, " \t"); if (s) { strtok(NULL, " \t#"); /* rest of the line */ if (*s == '/') /* absolute path */ initconf(0, s); else { char *path = MyMalloc( strlen(IRCDCONF_DIR) + strlen(s) + 3 ); if (!path) { fprintf(stderr, "initconf(): out of memory"); return NULL; } strcpy(path, IRCDCONF_DIR "/"); strcat(path, s); initconf(0, path); MyFree( path ); } } else fprintf(stderr, "initconf(): bad include: %s", line); } *line = '\0'; /* nullify it */ continue; } #endif if (line[1] != IRCDCONF_DELIMITER) { (void)fprintf(stderr, "ERROR: Bad config line (%s)\n", line); (void)fprintf(stderr, "\tWrong delimiter? (should be %c)\n", IRCDCONF_DELIMITER); continue; } if (debugflag) (void)printf("\n%s\n",line); (void)fflush(stdout); tmp = getfield(line); if (!tmp) { (void)fprintf(stderr, "\tERROR: no fields found\n"); continue; } aconf->status = CONF_ILLEGAL; switch (*tmp) { case 'A': /* Name, e-mail address of administrator */ case 'a': /* of this server. */ aconf->status = CONF_ADMIN; mandatory_found |= CONF_ADMIN; break; case 'B': /* bounce line */ case 'b': aconf->status = CONF_BOUNCE; break; case 'C': /* Server where I should try to connect */ case 'c': /* in case of lp failures */ ccount++; aconf->status = CONF_CONNECT_SERVER; break; case 'D': /* auto connect restrictions */ case 'd': aconf->status = CONF_DENY; break; #ifdef RUSNET_IRCD case 'E': case 'e': aconf->status = CONF_EXEMPT; break; case 'F': case 'f': aconf->status = CONF_INTERFACE; break; #endif case 'H': /* Hub server line */ case 'h': aconf->status = CONF_HUB; break; case 'I': /* Just plain normal irc client trying */ case 'i': /* to connect me */ aconf->status = CONF_CLIENT; mandatory_found |= CONF_CLIENT; break; case 'K': /* Kill user line on irc.conf */ case 'k': aconf->status = CONF_KILL; break; /* Operator. Line should contain at least */ /* password and host where connection is */ case 'L': /* guaranteed leaf server */ case 'l': aconf->status = CONF_LEAF; break; /* Me. Host field is name used for this host */ /* and port number is the number of the port */ case 'M': case 'm': aconf->status = CONF_ME; mandatory_found |= CONF_ME; break; case 'N': /* Server where I should NOT try to */ case 'n': /* connect in case of lp failures */ /* but which tries to connect ME */ ++ncount; aconf->status = CONF_NOCONNECT_SERVER; break; case 'O': aconf->status = CONF_OPERATOR; break; /* Local Operator, (limited privs --SRB) */ case 'o': aconf->status = CONF_LOCOP; break; case 'P': /* listen port line */ case 'p': aconf->status = CONF_LISTEN_PORT; mandatory_found |= CONF_LISTEN_PORT; break; case 'Q': /* a server that you don't want in your */ case 'q': /* network. USE WITH CAUTION! */ aconf->status = CONF_QUARANTINED_SERVER; break; #ifdef R_LINES case 'R': /* extended K line */ case 'r': /* Offers more options of how to restrict */ aconf->status = CONF_RESTRICT; break; #endif case 'S': /* Service. Same semantics as */ case 's': /* CONF_OPERATOR */ aconf->status = CONF_SERVICE; break; case 'U': /* Uphost, ie. host where client reading */ case 'u': /* this should connect. */ /* This is for client only, I must ignore this */ /* ...U-line should be removed... --msa */ break; case 'V': aconf->status = CONF_VER; break; case 'Y': case 'y': aconf->status = CONF_CLASS; break; default: (void)fprintf(stderr, "\tERROR: unknown conf line letter (%c)\n", *tmp); break; } if (IsIllegal(aconf)) continue; for (;;) /* Fake loop, that I can use break here --msa */ { if ((tmp = getfield(NULL)) == NULL) break; DupString(aconf->host, tmp); if ((tmp = getfield(NULL)) == NULL) break; DupString(aconf->passwd, tmp); if ((tmp = getfield(NULL)) == NULL) break; DupString(aconf->name, tmp); if ((tmp = getfield(NULL)) == NULL) break; aconf->port = atoi(tmp); if ((tmp = getfield(NULL)) == NULL) break; if (!(aconf->status & CONF_CLASS)) aconf->class = get_class(atoi(tmp)); break; } if (!aconf->class && (aconf->status & (CONF_CONNECT_SERVER| CONF_NOCONNECT_SERVER|CONF_OPS|CONF_CLIENT))) { (void)fprintf(stderr, "\tWARNING: No class. Default 0\n"); aconf->class = get_class(0); } /* ** If conf line is a class definition, create a class entry ** for it and make the conf_line illegal and delete it. */ if (aconf->status & CONF_CLASS) { if (!aconf->host) { (void)fprintf(stderr,"\tERROR: no class #\n"); continue; } if (!tmp) { (void)fprintf(stderr, "\tWARNING: missing sendq field\n"); (void)fprintf(stderr, "\t\t default: %d\n", QUEUELEN); (void)sprintf(maxsendq, "%d", QUEUELEN); } else (void)sprintf(maxsendq, "%d", atoi(tmp)); new_class(atoi(aconf->host)); aconf->class = get_class(atoi(aconf->host)); goto print_confline; } if (aconf->status & CONF_LISTEN_PORT) { #ifdef UNIXPORT struct stat sb; if (!aconf->host) (void)fprintf(stderr, "\tERROR: %s\n", "null host field in P-line"); else if (index(aconf->host, '/')) { if (stat(aconf->host, &sb) == -1) { (void)fprintf(stderr, "\tERROR: (%s) ", aconf->host); perror("stat"); } else if ((sb.st_mode & S_IFMT) != S_IFDIR) (void)fprintf(stderr, "\tERROR: %s not directory\n", aconf->host); } #else if (!aconf->host) (void)fprintf(stderr, "\tERROR: %s\n", "null host field in P-line"); else if (index(aconf->host, '/')) (void)fprintf(stderr, "\t%s %s\n", "WARNING: / present in P-line", "for non-UNIXPORT configuration"); #endif aconf->class = get_class(0); goto print_confline; } if (aconf->status & CONF_SERVER_MASK && (!aconf->host || index(aconf->host, '*') || index(aconf->host, '?'))) { (void)fprintf(stderr, "\tERROR: bad host field\n"); continue; } if (aconf->status & CONF_SERVER_MASK && BadPtr(aconf->passwd)) { (void)fprintf(stderr, "\tERROR: empty/no password field\n"); continue; } if (aconf->status & CONF_SERVER_MASK && !aconf->name) { (void)fprintf(stderr, "\tERROR: bad name field\n"); continue; } if (aconf->status & (CONF_SERVER_MASK|CONF_OPS)) if (!index(aconf->host, '@')) { char *newhost; int len = 3; /* *@\0 = 3 */ len += strlen(aconf->host); newhost = (char *)MyMalloc(len); (void)sprintf(newhost, "*@%s", aconf->host); (void)free(aconf->host); aconf->host = newhost; } #ifdef RUSNET_IRCD if (aconf->status & CONF_INTERFACE) { if (!aconf->host) { (void)fprintf(stderr, "X: ERROR: bad target IP field\n"); continue; } if (!aconf->name) { (void)fprintf(stderr, "X: ERROR: bad source IP field\n"); continue; } } #endif if (!aconf->class) aconf->class = get_class(0); (void)sprintf(maxsendq, "%d", aconf->class->class); if (!aconf->name) aconf->name = nullfield; if (!aconf->passwd) aconf->passwd = nullfield; if (!aconf->host) aconf->host = nullfield; if (aconf->status & (CONF_ME|CONF_ADMIN)) { if (flags & aconf->status) (void)fprintf(stderr, "ERROR: multiple %c-lines\n", toupper(confchar(aconf->status))); else flags |= aconf->status; } if (aconf->status & CONF_VER) { if (*aconf->host && !index(aconf->host, '/')) (void)fprintf(stderr, "\tWARNING: No / in V line."); else if (*aconf->passwd && !index(aconf->passwd, '/')) (void)fprintf(stderr, "\tWARNING: No / in V line."); } print_confline: if (debugflag > 8) (void)printf("(%d) (%s) (%s) (%s) (%d) (%s)\n", aconf->status, aconf->host, aconf->passwd, aconf->name, aconf->port, maxsendq); (void)fflush(stdout); if (aconf->status & (CONF_SERVER_MASK|CONF_HUB|CONF_LEAF)) { aconf->next = ctop; ctop = aconf; aconf = NULL; } } (void)close(fd); #ifdef M4_PREPROC (void)wait(0); #endif if (!(mandatory_found & CONF_ME)) { fprintf(stderr, "No M-line found (mandatory)\n"); valid = 0; } if (!(mandatory_found & CONF_ADMIN)) { fprintf(stderr, "No A-line found (mandatory)\n"); valid = 0; } if (!(mandatory_found & CONF_LISTEN_PORT)) { fprintf(stderr, "No P-line found (mandatory)\n"); valid = 0; } if (!(mandatory_found & CONF_CLIENT)) { fprintf(stderr, "No I-line found (mandatory)\n"); valid = 0; } if (!valid) { return NULL; } return ctop; } static aClass *get_class(cn) int cn; { static aClass cls; int i = numclasses - 1; cls.class = -1; for (; i >= 0; i--) if (classarr[i] == cn) { cls.class = cn; break; } if (i == -1) (void)fprintf(stderr,"\tWARNING: class %d not found\n", cn); return &cls; } static void new_class(cn) int cn; { numclasses++; if (classarr) classarr = (int *)realloc(classarr, sizeof(int) * numclasses); else classarr = (int *)malloc(sizeof(int)); classarr[numclasses-1] = cn; } /* * field breakup for ircd.conf file. */ static char *getfield(irc_newline) char *irc_newline; { static char *line = NULL; char *end, *field; if (irc_newline) line = irc_newline; if (line == NULL) return(NULL); field = line; if ((end = (char *)index(line, IRCDCONF_DELIMITER)) == NULL) { line = NULL; if ((end = (char *)index(field,'\n')) == NULL) end = field + strlen(field); } else line = end + 1; *end = '\0'; return(field); } /* ** read a string terminated by \r or \n in from a fd ** ** Created: Sat Dec 12 06:29:58 EST 1992 by avalon ** Changed: Thu Jul 3 13:38:58 MSD 2003 by erra ** Returns: ** 0 - EOF ** -1 - error on read ** >0 - number of bytes returned (<=num) ** After opening a fd, it is necessary to init dgets() by calling it as ** dgets(x,y,0); ** to mark the buffer as being empty. */ int dgets(fd, buf, num, head, tail) int fd, num; char *buf, **head, **tail; { register char *s, *t; register int n, nr; /* ** Sanity checks. */ if (!num) { *head = *tail = buf; **head = '\0'; return 0; } if (*head == *tail) **head = '\0'; dgetsagain: if (*head > buf) { for (nr = *tail - *head, s = *head, t = buf; nr > 0; nr--) *t++ = *s++; *tail = t; *head = buf; } /* ** check input buffer for EOL and if present return string. */ if (*head < *tail && ((s = index(*head, '\n')) || (s = index(*head, '\r'))) && s < *tail) { n = MIN(s - *head + 1, num); /* at least 1 byte */ dgetsreturnbuf: *head += n; if (*head == *tail) *head = *tail = buf; return n; } if (*tail - *head >= num) /* dgets buf is big enough */ { n = num; goto dgetsreturnbuf; } n = num - (*tail - buf) - 1; nr = read(fd, *tail, n); if (nr == -1) { *head = *tail = buf; return -1; } if (!nr) { if (*tail > *head) { n = MIN(*tail - *head, num); goto dgetsreturnbuf; } *head = *tail = buf; return 0; } *tail += nr; **tail = '\0'; for (t = *head; (s = index(t, '\n')); ) { if ((s > *head) && (s > buf)) { t = s-1; for (nr = 0; *t == '\\'; nr++) t--; if (nr & 1) { t = s+1; s--; nr = *tail - t; while (nr--) *s++ = *t++; *tail -= 2; **tail = '\0'; } else s++; } else s++; t = s; } **tail = '\0'; goto dgetsagain; } static int validate(top) aConfItem *top; { Reg aConfItem *aconf, *bconf; u_int otype = 0, valid = 0; if (!top) return -1; for (aconf = top; aconf; aconf = aconf->next) { if (aconf->status & CONF_MATCH) continue; if (aconf->status & CONF_SERVER_MASK) { if (aconf->status & CONF_CONNECT_SERVER) otype = CONF_NOCONNECT_SERVER; else if (aconf->status & CONF_NOCONNECT_SERVER) otype = CONF_CONNECT_SERVER; for (bconf = top; bconf; bconf = bconf->next) { if (bconf == aconf || !(bconf->status & otype)) continue; if (bconf->class == aconf->class && !strcasecmp(bconf->name, aconf->name) && !strcasecmp(bconf->host, aconf->host)) { aconf->status |= CONF_MATCH; bconf->status |= CONF_MATCH; break; } } } else for (bconf = top; bconf; bconf = bconf->next) { if ((bconf == aconf) || !(bconf->status & CONF_SERVER_MASK)) continue; if (!strcasecmp(bconf->name, aconf->name)) { aconf->status |= CONF_MATCH; break; } } } (void) fprintf(stderr, "\n"); for (aconf = top; aconf; aconf = aconf->next) if (aconf->status & CONF_MATCH) valid++; else (void)fprintf(stderr, "Unmatched %c:%s:%s:%s\n", confchar(aconf->status), aconf->host, SHOWSTR(aconf->passwd), aconf->name); return valid ? 0 : -1; } static char confchar(status) u_int status; { static char letrs[] = "QIiCcNoOMKARYSLPHV"; char *s = letrs; status &= ~(CONF_MATCH|CONF_ILLEGAL); for (; *s; s++, status >>= 1) if (status & 1) return *s; return '-'; } void outofmemory() { (void)write(2, "Out of memory\n", 14); exit(-1); }