This commit was generated by cvs2svn to compensate for changes in r2,
[ircu2.10.12-pk.git] / ircd / s_conf.c
1 /*
2  * IRC - Internet Relay Chat, ircd/s_conf.c
3  * Copyright (C) 1990 Jarkko Oikarinen and
4  *                    University of Oulu, Computing Center
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 1, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20
21 #include "sys.h"
22 #include <sys/socket.h>
23 #if HAVE_FCNTL_H
24 #include <fcntl.h>
25 #endif
26
27 #if HAVE_SYS_WAIT_H
28 # include <sys/wait.h>
29 #endif
30 #ifndef WEXITSTATUS
31 # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
32 #endif
33 #ifndef WIFEXITED
34 # define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
35 #endif
36
37 #include <sys/stat.h>
38 #ifdef R_LINES
39 #include <signal.h>
40 #endif
41 #if HAVE_UNISTD_H
42 #include <unistd.h>
43 #endif
44 #include <stdlib.h>
45 #include <netdb.h>
46 #include <netinet/in.h>
47 #include <arpa/inet.h>
48 #ifdef USE_SYSLOG
49 #include <syslog.h>
50 #endif
51 #include "h.h"
52 #include "struct.h"
53 #include "s_serv.h"
54 #include "opercmds.h"
55 #include "numeric.h"
56 #include "send.h"
57 #include "s_conf.h"
58 #include "class.h"
59 #include "s_misc.h"
60 #include "match.h"
61 #include "common.h"
62 #include "s_err.h"
63 #include "s_bsd.h"
64 #include "ircd.h"
65 #include "crule.h"
66 #include "res.h"
67 #include "support.h"
68 #include "parse.h"
69 #include "numnicks.h"
70 #include "sprintf_irc.h"
71 #include "IPcheck.h"
72 #include "hash.h"
73 #include "fileio.h"
74
75 RCSTAG_CC("$Id$");
76
77 static int check_time_interval(char *, char *);
78 static int lookup_confhost(aConfItem *);
79 static int is_comment(char *);
80 static void killcomment(aClient *sptr, char *parv, char *filename);
81
82 aConfItem *conf = NULL;
83 aGline *gline = NULL;
84 aMotdItem *motd = NULL;
85 aMotdItem *rmotd = NULL;
86 atrecord *tdata = NULL;
87 struct tm motd_tm;
88
89 /*
90  * field breakup for ircd.conf file.
91  */
92 static char *gfline = NULL;
93 char *getfield(char *newline, char fs)
94 {
95   char *end, *field;
96
97   if (newline)
98     gfline = newline;
99
100   if (gfline == NULL)
101     return NULL;
102
103   end = field = gfline;
104
105   if (fs != ':')
106   {
107     if (*end == fs)
108       ++end;
109     else
110       fs = ':';
111   }
112   do
113   {
114     while (*end != fs)
115     {
116       if (!*end)
117       {
118         end = NULL;
119         break;
120       }
121       ++end;
122     }
123   }
124   while (end && fs != ':' && *++end != ':' && *end != '\n');
125
126   if (end == NULL)
127   {
128     gfline = NULL;
129     if ((end = strchr(field, '\n')) == NULL)
130       end = field + strlen(field);
131   }
132   else
133     gfline = end + 1;
134
135   *end = '\0';
136
137   return field;
138 }
139
140 /*
141  * Remove all conf entries from the client except those which match
142  * the status field mask.
143  */
144 void det_confs_butmask(aClient *cptr, int mask)
145 {
146   Reg1 Link *tmp, *tmp2;
147
148   for (tmp = cptr->confs; tmp; tmp = tmp2)
149   {
150     tmp2 = tmp->next;
151     if ((tmp->value.aconf->status & mask) == 0)
152       detach_conf(cptr, tmp->value.aconf);
153   }
154 }
155
156 /*
157  * Find the first (best) I line to attach.
158  */
159 enum AuthorizationCheckResult attach_Iline(aClient *cptr, struct hostent *hp,
160     char *sockhost)
161 {
162   Reg1 aConfItem *aconf;
163   Reg3 const char *hname;
164   Reg4 int i;
165   static char uhost[HOSTLEN + USERLEN + 3];
166   static char fullname[HOSTLEN + 1];
167
168   for (aconf = conf; aconf; aconf = aconf->next)
169   {
170     if (aconf->status != CONF_CLIENT)
171       continue;
172     if (aconf->port && aconf->port != cptr->acpt->port)
173       continue;
174     if (!aconf->host || !aconf->name)
175       continue;
176     if (hp)
177       for (i = 0, hname = hp->h_name; hname; hname = hp->h_aliases[i++])
178       {
179         size_t fullnamelen = 0;
180         size_t label_count = 0;
181         int error;
182
183         strncpy(fullname, hname, HOSTLEN);
184         fullname[HOSTLEN] = 0;
185
186         /*
187          * Disallow a hostname label to contain anything but a [-a-zA-Z0-9].
188          * It may not start or end on a '.'.
189          * A label may not end on a '-', the maximum length of a label is
190          * 63 characters.
191          * On top of that (which seems to be the RFC) we demand that the
192          * top domain does not contain any digits.
193          */
194         error = (*hname == '.') ? 1 : 0;        /* May not start with a '.' */
195         if (!error)
196         {
197           char *p;
198           for (p = fullname; *p; ++p, ++fullnamelen)
199           {
200             if (*p == '.')
201             {
202               if (p[-1] == '-'  /* Label may not end on '-' */
203                   || p[1] == 0) /* May not end on a '.' */
204               {
205                 error = 1;
206                 break;
207               }
208               label_count = 0;
209               error = 0;        /* Was not top domain */
210               continue;
211             }
212             if (++label_count > 63)     /* Label not longer then 63 */
213             {
214               error = 1;
215               break;
216             }
217             if (*p >= '0' && *p <= '9')
218             {
219               error = 1;        /* In case this is top domain */
220               continue;
221             }
222             if (!(*p >= 'a' && *p <= 'z')
223                 && !(*p >= 'A' && *p <= 'Z') && *p != '-')
224             {
225               error = 1;
226               break;
227             }
228           }
229         }
230         if (error)
231         {
232           hp = NULL;
233           break;
234         }
235
236         add_local_domain(fullname, HOSTLEN - fullnamelen);
237         Debug((DEBUG_DNS, "a_il: %s->%s", sockhost, fullname));
238         if (strchr(aconf->name, '@'))
239         {
240           strcpy(uhost, cptr->username);
241           strcat(uhost, "@");
242         }
243         else
244           *uhost = '\0';
245         strncat(uhost, fullname, sizeof(uhost) - 1 - strlen(uhost));
246         uhost[sizeof(uhost) - 1] = 0;
247         if (!match(aconf->name, uhost))
248         {
249           if (strchr(uhost, '@'))
250             cptr->flags |= FLAGS_DOID;
251           goto attach_iline;
252         }
253       }
254
255     if (strchr(aconf->host, '@'))
256     {
257       strncpy(uhost, cptr->username, sizeof(uhost) - 2);
258       uhost[sizeof(uhost) - 2] = 0;
259       strcat(uhost, "@");
260     }
261     else
262       *uhost = '\0';
263     strncat(uhost, sockhost, sizeof(uhost) - 1 - strlen(uhost));
264     uhost[sizeof(uhost) - 1] = 0;
265     if (match(aconf->host, uhost))
266       continue;
267     if (strchr(uhost, '@'))
268       cptr->flags |= FLAGS_DOID;
269     if (hp && hp->h_name)
270     {
271       strncpy(uhost, hp->h_name, HOSTLEN);
272       uhost[HOSTLEN] = 0;
273       add_local_domain(uhost, HOSTLEN - strlen(uhost));
274     }
275   attach_iline:
276     get_sockhost(cptr, uhost);
277
278     if (aconf->passwd)
279     {
280       if (isDigit(*aconf->passwd) && !aconf->passwd[1]) /* Special case: exactly one digit */
281       {
282         /* Refuse connections when there are already <digit> clients connected with the same IP number */
283         unsigned short nr = *aconf->passwd - '0';
284         if (IPcheck_nr(cptr) > nr)
285           return ACR_TOO_MANY_FROM_IP;  /* Already got nr with that ip# */
286       }
287 #ifdef USEONE
288       else if (!strcmp(aconf->passwd, "ONE"))
289       {
290         for (i = highest_fd; i >= 0; i--)
291           if (loc_clients[i] && MyUser(loc_clients[i]) &&
292               loc_clients[i]->ip.s_addr == cptr->ip.s_addr)
293             return ACR_TOO_MANY_FROM_IP;        /* Already got one with that ip# */
294       }
295 #endif
296     }
297     return attach_conf(cptr, aconf);
298   }
299   return ACR_NO_AUTHORIZATION;
300 }
301
302 /*
303  * Find the single N line and return pointer to it (from list).
304  * If more than one then return NULL pointer.
305  */
306 aConfItem *count_cnlines(Link *lp)
307 {
308   Reg1 aConfItem *aconf, *cline = NULL, *nline = NULL;
309
310   for (; lp; lp = lp->next)
311   {
312     aconf = lp->value.aconf;
313     if (!(aconf->status & CONF_SERVER_MASK))
314       continue;
315     if (aconf->status == CONF_CONNECT_SERVER && !cline)
316       cline = aconf;
317     else if (aconf->status == CONF_NOCONNECT_SERVER && !nline)
318       nline = aconf;
319   }
320   return nline;
321 }
322
323 /*
324  * detach_conf
325  *
326  * Disassociate configuration from the client.
327  */
328 int detach_conf(aClient *cptr, aConfItem *aconf)
329 {
330   Reg1 Link **lp, *tmp;
331
332   lp = &(cptr->confs);
333
334   while (*lp)
335   {
336     if ((*lp)->value.aconf == aconf)
337     {
338       if (aconf && (aconf->confClass)
339           && (aconf->status & CONF_CLIENT_MASK) && ConfLinks(aconf) > 0)
340         --ConfLinks(aconf);
341       if (aconf && !--aconf->clients && IsIllegal(aconf))
342         free_conf(aconf);
343       tmp = *lp;
344       *lp = tmp->next;
345       free_link(tmp);
346       return 0;
347     }
348     else
349       lp = &((*lp)->next);
350   }
351   return -1;
352 }
353
354 static int is_attached(aConfItem *aconf, aClient *cptr)
355 {
356   Reg1 Link *lp;
357
358   for (lp = cptr->confs; lp; lp = lp->next)
359     if (lp->value.aconf == aconf)
360       break;
361
362   return (lp) ? 1 : 0;
363 }
364
365 /*
366  * attach_conf
367  *
368  * Associate a specific configuration entry to a *local*
369  * client (this is the one which used in accepting the
370  * connection). Note, that this automaticly changes the
371  * attachment if there was an old one...
372  */
373 enum AuthorizationCheckResult attach_conf(aClient *cptr, aConfItem *aconf)
374 {
375   Reg1 Link *lp;
376
377   if (is_attached(aconf, cptr))
378     return ACR_ALREADY_AUTHORIZED;
379   if (IsIllegal(aconf))
380     return ACR_NO_AUTHORIZATION;
381   if ((aconf->status & (CONF_LOCOP | CONF_OPERATOR | CONF_CLIENT)) &&
382       ConfLinks(aconf) >= ConfMaxLinks(aconf) && ConfMaxLinks(aconf) > 0)
383     return ACR_TOO_MANY_IN_CLASS;       /* Use this for printing error message */
384   lp = make_link();
385   lp->next = cptr->confs;
386   lp->value.aconf = aconf;
387   cptr->confs = lp;
388   aconf->clients++;
389   if (aconf->status & CONF_CLIENT_MASK)
390     ConfLinks(aconf)++;
391   return ACR_OK;
392 }
393
394 aConfItem *find_admin(void)
395 {
396   Reg1 aConfItem *aconf;
397
398   for (aconf = conf; aconf; aconf = aconf->next)
399     if (aconf->status & CONF_ADMIN)
400       break;
401
402   return (aconf);
403 }
404
405 aConfItem *find_me(void)
406 {
407   Reg1 aConfItem *aconf;
408   for (aconf = conf; aconf; aconf = aconf->next)
409     if (aconf->status & CONF_ME)
410       break;
411
412   return (aconf);
413 }
414
415 /*
416  * attach_confs
417  *
418  * Attach a CONF line to a client if the name passed matches that for
419  * the conf file (for non-C/N lines) or is an exact match (C/N lines
420  * only).  The difference in behaviour is to stop C:*::* and N:*::*.
421  */
422 aConfItem *attach_confs(aClient *cptr, const char *name, int statmask)
423 {
424   Reg1 aConfItem *tmp;
425   aConfItem *first = NULL;
426   int len = strlen(name);
427
428   if (!name || len > HOSTLEN)
429     return NULL;
430   for (tmp = conf; tmp; tmp = tmp->next)
431   {
432     if ((tmp->status & statmask) && !IsIllegal(tmp) &&
433         ((tmp->status & (CONF_SERVER_MASK | CONF_HUB)) == 0) &&
434         tmp->name && !match(tmp->name, name))
435     {
436       if (attach_conf(cptr, tmp) == ACR_OK && !first)
437         first = tmp;
438     }
439     else if ((tmp->status & statmask) && !IsIllegal(tmp) &&
440         (tmp->status & (CONF_SERVER_MASK | CONF_HUB)) &&
441         tmp->name && !strCasediff(tmp->name, name))
442     {
443       if (attach_conf(cptr, tmp) == ACR_OK && !first)
444         first = tmp;
445     }
446   }
447   return (first);
448 }
449
450 /*
451  * Added for new access check    meLazy
452  */
453 aConfItem *attach_confs_host(aClient *cptr, char *host, int statmask)
454 {
455   Reg1 aConfItem *tmp;
456   aConfItem *first = NULL;
457   int len = strlen(host);
458
459   if (!host || len > HOSTLEN)
460     return NULL;
461
462   for (tmp = conf; tmp; tmp = tmp->next)
463   {
464     if ((tmp->status & statmask) && !IsIllegal(tmp) &&
465         (tmp->status & CONF_SERVER_MASK) == 0 &&
466         (!tmp->host || match(tmp->host, host) == 0))
467     {
468       if (attach_conf(cptr, tmp) == ACR_OK && !first)
469         first = tmp;
470     }
471     else if ((tmp->status & statmask) && !IsIllegal(tmp) &&
472         (tmp->status & CONF_SERVER_MASK) &&
473         (tmp->host && strCasediff(tmp->host, host) == 0))
474     {
475       if (attach_conf(cptr, tmp) == ACR_OK && !first)
476         first = tmp;
477     }
478   }
479   return (first);
480 }
481
482 /*
483  * find a conf entry which matches the hostname and has the same name.
484  */
485 aConfItem *find_conf_exact(char *name, char *user, char *host, int statmask)
486 {
487   Reg1 aConfItem *tmp;
488   char userhost[USERLEN + HOSTLEN + 3];
489
490   sprintf_irc(userhost, "%s@%s", user, host);
491
492   for (tmp = conf; tmp; tmp = tmp->next)
493   {
494     if (!(tmp->status & statmask) || !tmp->name || !tmp->host ||
495         strCasediff(tmp->name, name))
496       continue;
497     /*
498      * Accept if the *real* hostname (usually sockecthost)
499      * socket host) matches *either* host or name field
500      * of the configuration.
501      */
502     if (match(tmp->host, userhost))
503       continue;
504     if (tmp->status & (CONF_OPERATOR | CONF_LOCOP))
505     {
506       if (tmp->clients < MaxLinks(tmp->confClass))
507         return tmp;
508       else
509         continue;
510     }
511     else
512       return tmp;
513   }
514   return NULL;
515 }
516
517 aConfItem *find_conf(Link *lp, const char *name, int statmask)
518 {
519   Reg1 aConfItem *tmp;
520   int namelen = name ? strlen(name) : 0;
521
522   if (namelen > HOSTLEN)
523     return (aConfItem *)0;
524
525   for (; lp; lp = lp->next)
526   {
527     tmp = lp->value.aconf;
528     if ((tmp->status & statmask) &&
529         (((tmp->status & (CONF_SERVER_MASK | CONF_HUB)) &&
530         tmp->name && !strCasediff(tmp->name, name)) ||
531         ((tmp->status & (CONF_SERVER_MASK | CONF_HUB)) == 0 &&
532         tmp->name && !match(tmp->name, name))))
533       return tmp;
534   }
535   return NULL;
536 }
537
538 /*
539  * Added for new access check    meLazy
540  */
541 aConfItem *find_conf_host(Link *lp, char *host, int statmask)
542 {
543   Reg1 aConfItem *tmp;
544   int hostlen = host ? strlen(host) : 0;
545
546   if (hostlen > HOSTLEN || BadPtr(host))
547     return (aConfItem *)NULL;
548   for (; lp; lp = lp->next)
549   {
550     tmp = lp->value.aconf;
551     if (tmp->status & statmask &&
552         (!(tmp->status & CONF_SERVER_MASK || tmp->host) ||
553         (tmp->host && !match(tmp->host, host))))
554       return tmp;
555   }
556   return NULL;
557 }
558
559 /*
560  * find_conf_ip
561  *
562  * Find a conf line using the IP# stored in it to search upon.
563  * Added 1/8/92 by Avalon.
564  */
565 aConfItem *find_conf_ip(Link *lp, char *ip, char *user, int statmask)
566 {
567   Reg1 aConfItem *tmp;
568   Reg2 char *s;
569
570   for (; lp; lp = lp->next)
571   {
572     tmp = lp->value.aconf;
573     if (!(tmp->status & statmask))
574       continue;
575     s = strchr(tmp->host, '@');
576     *s = '\0';
577     if (match(tmp->host, user))
578     {
579       *s = '@';
580       continue;
581     }
582     *s = '@';
583     if (!memcmp(&tmp->ipnum, ip, sizeof(struct in_addr)))
584       return tmp;
585   }
586   return NULL;
587 }
588
589 /*
590  * find_conf_entry
591  *
592  * - looks for a match on all given fields.
593  */
594 static aConfItem *find_conf_entry(aConfItem *aconf, unsigned int mask)
595 {
596   Reg1 aConfItem *bconf;
597
598   for (bconf = conf, mask &= ~CONF_ILLEGAL; bconf; bconf = bconf->next)
599   {
600     if (!(bconf->status & mask) || (bconf->port != aconf->port))
601       continue;
602
603     if ((BadPtr(bconf->host) && !BadPtr(aconf->host)) ||
604         (BadPtr(aconf->host) && !BadPtr(bconf->host)))
605       continue;
606     if (!BadPtr(bconf->host) && strCasediff(bconf->host, aconf->host))
607       continue;
608
609     if ((BadPtr(bconf->passwd) && !BadPtr(aconf->passwd)) ||
610         (BadPtr(aconf->passwd) && !BadPtr(bconf->passwd)))
611       continue;
612     if (!BadPtr(bconf->passwd) && (!isDigit(*bconf->passwd) || bconf->passwd[1])
613 #ifdef USEONE
614         && strCasediff(bconf->passwd, "ONE")
615 #endif
616         && strCasediff(bconf->passwd, aconf->passwd))
617       continue;
618
619     if ((BadPtr(bconf->name) && !BadPtr(aconf->name)) ||
620         (BadPtr(aconf->name) && !BadPtr(bconf->name)))
621       continue;
622     if (!BadPtr(bconf->name) && strCasediff(bconf->name, aconf->name))
623       continue;
624     break;
625   }
626   return bconf;
627 }
628
629 /*
630  * rehash
631  *
632  * Actual REHASH service routine. Called with sig == 0 if it has been called
633  * as a result of an operator issuing this command, else assume it has been
634  * called as a result of the server receiving a HUP signal.
635  */
636 int rehash(aClient *cptr, int sig)
637 {
638   Reg1 aConfItem **tmp = &conf, *tmp2;
639   Reg2 aConfClass *cltmp;
640   Reg1 aClient *acptr;
641   Reg2 aMotdItem *temp;
642   Reg2 int i;
643   int ret = 0, found_g;
644
645   if (sig == 1)
646     sendto_ops("Got signal SIGHUP, reloading ircd conf. file");
647
648   for (i = 0; i <= highest_fd; i++)
649     if ((acptr = loc_clients[i]) && !IsMe(acptr))
650     {
651       /*
652        * Nullify any references from client structures to
653        * this host structure which is about to be freed.
654        * Could always keep reference counts instead of
655        * this....-avalon
656        */
657       acptr->hostp = NULL;
658     }
659
660   while ((tmp2 = *tmp))
661     if (tmp2->clients || tmp2->status & CONF_LISTEN_PORT)
662     {
663       /*
664        * Configuration entry is still in use by some
665        * local clients, cannot delete it--mark it so
666        * that it will be deleted when the last client
667        * exits...
668        */
669       if (!(tmp2->status & (CONF_LISTEN_PORT | CONF_CLIENT)))
670       {
671         *tmp = tmp2->next;
672         tmp2->next = NULL;
673       }
674       else
675         tmp = &tmp2->next;
676       tmp2->status |= CONF_ILLEGAL;
677     }
678     else
679     {
680       *tmp = tmp2->next;
681       /* free expression trees of connect rules */
682       if ((tmp2->status & (CONF_CRULEALL | CONF_CRULEAUTO)) &&
683           (tmp2->passwd != NULL))
684         crule_free(&(tmp2->passwd));
685       free_conf(tmp2);
686     }
687
688   /*
689    * We don't delete the class table, rather mark all entries
690    * for deletion. The table is cleaned up by check_class(). - avalon
691    */
692   for (cltmp = NextClass(FirstClass()); cltmp; cltmp = NextClass(cltmp))
693     MarkDelete(cltmp);
694
695   /*
696    * delete the juped nicks list
697    */
698   clearNickJupes();
699
700   if (sig != 2)
701     flush_cache();
702   if (initconf(0) == -1)        /* This calls check_class(), */
703     check_class();              /* unless it fails */
704   close_listeners();
705
706   /*
707    * Flush out deleted I and P lines although still in use.
708    */
709   for (tmp = &conf; (tmp2 = *tmp);)
710     if (!(tmp2->status & CONF_ILLEGAL))
711       tmp = &tmp2->next;
712     else
713     {
714       *tmp = tmp2->next;
715       tmp2->next = NULL;
716       if (!tmp2->clients)
717         free_conf(tmp2);
718     }
719
720   for (i = 0; i <= highest_fd; i++) {
721     if ((acptr = loc_clients[i]) && !IsMe(acptr)) {
722       if (IsServer(acptr)) {
723         det_confs_butmask(acptr,
724             ~(CONF_HUB | CONF_LEAF | CONF_UWORLD | CONF_ILLEGAL));
725         attach_confs(acptr, acptr->name, CONF_HUB | CONF_LEAF | CONF_UWORLD);
726       }
727       if ((found_g = find_kill(acptr))) {
728         sendto_op_mask(found_g == -2 ? SNO_GLINE : SNO_OPERKILL,
729             found_g == -2 ? "G-line active for %s" : "K-line active for %s",
730             get_client_name(acptr, FALSE));
731         if (exit_client(cptr, acptr, &me, found_g == -2 ? "G-lined" :
732             "K-lined") == CPTR_KILLED)
733           ret = CPTR_KILLED;
734       }
735 #if defined(R_LINES) && defined(R_LINES_REHASH) && !defined(R_LINES_OFTEN)
736       if (find_restrict(acptr)) {
737         sendto_ops("Restricting %s, closing lp", get_client_name(acptr, FALSE));
738         if (exit_client(cptr, acptr, &me, "R-lined") == CPTR_KILLED)
739           ret = CPTR_KILLED;
740       }
741 #endif
742     }
743   }
744
745   /* free old motd structs */
746   while (motd) {
747     temp = motd->next;
748     RunFree(motd);
749     motd = temp;
750   }
751   while (rmotd) {
752     temp = rmotd->next;
753     RunFree(rmotd);
754     rmotd = temp;
755   }
756   /* reload motd files */
757   read_tlines();
758   rmotd = read_motd(RPATH);
759   motd = read_motd(MPATH);
760
761   return ret;
762 }
763
764 /*
765  * initconf
766  *
767  * Read configuration file.
768  *
769  * returns -1, if file cannot be opened
770  *          0, if file opened
771  */
772
773 #define MAXCONFLINKS 150
774
775 unsigned short server_port;
776
777 int initconf(int opt)
778 {
779   static char quotes[9][2] = {
780     {'b', '\b'},
781     {'f', '\f'},
782     {'n', '\n'},
783     {'r', '\r'},
784     {'t', '\t'},
785     {'v', '\v'},
786     {'\\', '\\'},
787     {0, 0}
788   };
789   Reg1 char *tmp, *s;
790   FBFILE *file;
791   int i;
792   char line[512];
793   int ccount = 0, ncount = 0;
794   aConfItem *aconf = NULL;
795
796   Debug((DEBUG_DEBUG, "initconf(): ircd.conf = %s", configfile));
797   if (NULL == (file = fbopen(configfile, "r")))
798   {
799     return -1;
800   }
801   while (fbgets(line, sizeof(line) - 1, file))
802   {
803     if ((tmp = strchr(line, '\n')))
804       *tmp = '\0';
805     /*
806      * Do quoting of characters and # detection.
807      */
808     for (tmp = line; *tmp; tmp++)
809     {
810       if (*tmp == '\\')
811       {
812         for (i = 0; quotes[i][0]; i++)
813           if (quotes[i][0] == *(tmp + 1))
814           {
815             *tmp = quotes[i][1];
816             break;
817           }
818         if (!quotes[i][0])
819           *tmp = *(tmp + 1);
820         if (!*(tmp + 1))
821           break;
822         else
823           for (s = tmp; (*s = *(s + 1)); s++)
824             ;
825       }
826       else if (*tmp == '#')
827         *tmp = '\0';
828     }
829     if (!*line || line[0] == '#' || line[0] == '\n' ||
830         line[0] == ' ' || line[0] == '\t')
831       continue;
832     /* Could we test if it's conf line at all?      -Vesa */
833     if (line[1] != ':')
834     {
835       Debug((DEBUG_ERROR, "Bad config line: %s", line));
836       continue;
837     }
838     if (aconf)
839       free_conf(aconf);
840     aconf = make_conf();
841
842     tmp = getfield(line, ':');
843     if (!tmp)
844       continue;
845     switch (*tmp)
846     {
847       case 'A':         /* Name, e-mail address of administrator */
848       case 'a':         /* of this server. */
849         aconf->status = CONF_ADMIN;
850         break;
851       case 'C':         /* Server where I should try to connect */
852       case 'c':         /* in case of lp failures             */
853         ccount++;
854         aconf->status = CONF_CONNECT_SERVER;
855         break;
856         /* Connect rule */
857       case 'D':
858         aconf->status = CONF_CRULEALL;
859         break;
860         /* Connect rule - autos only */
861       case 'd':
862         aconf->status = CONF_CRULEAUTO;
863         break;
864       case 'H':         /* Hub server line */
865       case 'h':
866         aconf->status = CONF_HUB;
867         break;
868       case 'I':         /* Just plain normal irc client trying  */
869       case 'i':         /* to connect me */
870         aconf->status = CONF_CLIENT;
871         break;
872       case 'K':         /* Kill user line on irc.conf           */
873         aconf->status = CONF_KILL;
874         break;
875       case 'k':         /* Kill user line based on IP in ircd.conf */
876         aconf->status = CONF_IPKILL;
877         break;
878         /* Operator. Line should contain at least */
879         /* password and host where connection is  */
880       case 'L':         /* guaranteed leaf server */
881       case 'l':
882         aconf->status = CONF_LEAF;
883         break;
884         /* Me. Host field is name used for this host */
885         /* and port number is the number of the port */
886       case 'M':
887       case 'm':
888         aconf->status = CONF_ME;
889         break;
890       case 'N':         /* Server where I should NOT try to     */
891       case 'n':         /* connect in case of lp failures     */
892         /* but which tries to connect ME        */
893         ++ncount;
894         aconf->status = CONF_NOCONNECT_SERVER;
895         break;
896       case 'O':
897         aconf->status = CONF_OPERATOR;
898         break;
899         /* Local Operator, (limited privs --SRB) */
900       case 'o':
901         aconf->status = CONF_LOCOP;
902         break;
903       case 'P':         /* listen port line */
904       case 'p':
905         aconf->status = CONF_LISTEN_PORT;
906         break;
907 #ifdef R_LINES
908       case 'R':         /* extended K line */
909       case 'r':         /* Offers more options of how to restrict */
910         aconf->status = CONF_RESTRICT;
911         break;
912 #endif
913       case 'T':         /* print out different motd's */
914       case 't':         /* based on hostmask */
915         aconf->status = CONF_TLINES;
916         break;
917       case 'U':         /* Underworld server, allowed to hack modes */
918       case 'u':         /* *Every* server on the net must define the same !!! */
919         aconf->status = CONF_UWORLD;
920         break;
921       case 'Y':
922       case 'y':
923         aconf->status = CONF_CLASS;
924         break;
925       default:
926         Debug((DEBUG_ERROR, "Error in config file: %s", line));
927         break;
928     }
929     if (IsIllegal(aconf))
930       continue;
931
932     for (;;)                    /* Fake loop, that I can use break here --msa */
933     {
934       if ((tmp = getfield(NULL, ':')) == NULL)
935         break;
936       DupString(aconf->host, tmp);
937       if ((tmp = getfield(NULL, (aconf->status == CONF_KILL
938           || aconf->status == CONF_IPKILL) ? '"' : ':')) == NULL)
939         break;
940       DupString(aconf->passwd, tmp);
941       if ((tmp = getfield(NULL, ':')) == NULL)
942         break;
943       DupString(aconf->name, tmp);
944       if ((tmp = getfield(NULL, ':')) == NULL)
945         break;
946       aconf->port = atoi(tmp);
947       tmp = getfield(NULL, ':');
948       if (aconf->status & CONF_ME)
949       {
950         server_port = aconf->port;
951         if (!tmp)
952         {
953           Debug((DEBUG_FATAL, "Your M: line must have the Numeric, "
954               "assigned to you by routing-com, behind the port number!\n"));
955 #ifdef USE_SYSLOG
956           syslog(LOG_WARNING, "Your M: line must have the Numeric, "
957               "assigned to you by routing-com, behind the port number!\n");
958 #endif
959           exit(-1);
960         }
961         SetYXXServerName(&me, atoi(tmp));       /* Our Numeric Nick */
962       }
963       else if (tmp)
964         aconf->confClass = find_class(atoi(tmp));
965       break;
966     }
967     /*
968      * If conf line is a class definition, create a class entry
969      * for it and make the conf_line illegal and delete it.
970      */
971     if (aconf->status & CONF_CLASS)
972     {
973       add_class(atoi(aconf->host), atoi(aconf->passwd),
974           atoi(aconf->name), aconf->port, tmp ? atoi(tmp) : 0);
975       continue;
976     }
977     /*
978      * Associate each conf line with a class by using a pointer
979      * to the correct class record. -avalon
980      */
981     if (aconf->status & (CONF_CLIENT_MASK | CONF_LISTEN_PORT))
982     {
983       if (aconf->confClass == 0)
984         aconf->confClass = find_class(0);
985     }
986     if (aconf->status & (CONF_LISTEN_PORT | CONF_CLIENT))
987     {
988       aConfItem *bconf;
989
990       if ((bconf = find_conf_entry(aconf, aconf->status)))
991       {
992         delist_conf(bconf);
993         bconf->status &= ~CONF_ILLEGAL;
994         if (aconf->status == CONF_CLIENT)
995         {
996           char *passwd = bconf->passwd;
997           bconf->passwd = aconf->passwd;
998           aconf->passwd = passwd;
999           ConfLinks(bconf) -= bconf->clients;
1000           bconf->confClass = aconf->confClass;
1001           if (bconf->confClass)
1002             ConfLinks(bconf) += bconf->clients;
1003         }
1004         free_conf(aconf);
1005         aconf = bconf;
1006       }
1007       else if (aconf->host && aconf->status == CONF_LISTEN_PORT)
1008         add_listener(aconf);
1009     }
1010     if (aconf->status & CONF_SERVER_MASK)
1011       if (ncount > MAXCONFLINKS || ccount > MAXCONFLINKS ||
1012           !aconf->host || strchr(aconf->host, '*') ||
1013           strchr(aconf->host, '?') || !aconf->name)
1014         continue;
1015
1016     if (aconf->status & (CONF_SERVER_MASK | CONF_LOCOP | CONF_OPERATOR))
1017       if (!strchr(aconf->host, '@') && *aconf->host != '/')
1018       {
1019         char *newhost;
1020         int len = 3;            /* *@\0 = 3 */
1021
1022         len += strlen(aconf->host);
1023         newhost = (char *)RunMalloc(len);
1024         sprintf_irc(newhost, "*@%s", aconf->host);
1025         RunFree(aconf->host);
1026         aconf->host = newhost;
1027       }
1028     if (aconf->status & CONF_SERVER_MASK)
1029     {
1030       if (BadPtr(aconf->passwd))
1031         continue;
1032       else if (!(opt & BOOT_QUICK))
1033         lookup_confhost(aconf);
1034     }
1035
1036     /* Create expression tree from connect rule...
1037      * If there's a parsing error, nuke the conf structure */
1038     if (aconf->status & (CONF_CRULEALL | CONF_CRULEAUTO))
1039     {
1040       RunFree(aconf->passwd);
1041       if ((aconf->passwd = (char *)crule_parse(aconf->name)) == NULL)
1042       {
1043         free_conf(aconf);
1044         aconf = NULL;
1045         continue;
1046       }
1047     }
1048
1049     /*
1050      * Own port and name cannot be changed after the startup.
1051      * (or could be allowed, but only if all links are closed first).
1052      * Configuration info does not override the name and port
1053      * if previously defined. Note, that "info"-field can be
1054      * changed by "/rehash".
1055      */
1056     if (aconf->status == CONF_ME)
1057     {
1058       strncpy(me.info, aconf->name, sizeof(me.info) - 1);
1059       if (me.name[0] == '\0' && aconf->host[0])
1060         strncpy(me.name, aconf->host, sizeof(me.name) - 1);
1061       if (portnum == 0)
1062         portnum = aconf->port;
1063     }
1064
1065     /*
1066      * Juped nicks are listed in the 'password' field of U:lines,
1067      * the list is comma separated and might be empty and/or contain
1068      * empty elements... the only limit is that it MUST be shorter
1069      * than 512 chars, or they will be cutted out :)
1070      */
1071     if ((aconf->status == CONF_UWORLD) && (aconf->passwd) && (*aconf->passwd))
1072       addNickJupes(aconf->passwd);
1073
1074     if (aconf->status & CONF_ADMIN)
1075       if (!aconf->host || !aconf->passwd || !aconf->name)
1076       {
1077         Debug((DEBUG_FATAL, "Your A: line must have 4 fields!\n"));
1078 #ifdef USE_SYSLOG
1079         syslog(LOG_WARNING, "Your A: line must have 4 fields!\n");
1080 #endif
1081         exit(-1);
1082       }
1083
1084     collapse(aconf->host);
1085     collapse(aconf->name);
1086     Debug((DEBUG_NOTICE,
1087         "Read Init: (%d) (%s) (%s) (%s) (%u) (%p)",
1088         aconf->status, aconf->host, aconf->passwd,
1089         aconf->name, aconf->port, aconf->confClass));
1090     aconf->next = conf;
1091     conf = aconf;
1092     aconf = NULL;
1093   }
1094   if (aconf)
1095     free_conf(aconf);
1096   fbclose(file);
1097   check_class();
1098   nextping = nextconnect = now;
1099   return 0;
1100 }
1101
1102 /*
1103  * lookup_confhost
1104  *
1105  * Do (start) DNS lookups of all hostnames in the conf line and convert
1106  * an IP addresses in a.b.c.d number for to IP#s.
1107  */
1108 static int lookup_confhost(aConfItem *aconf)
1109 {
1110   Reg2 char *s;
1111   Reg3 struct hostent *hp;
1112   Link ln;
1113
1114   if (BadPtr(aconf->host) || BadPtr(aconf->name))
1115     goto badlookup;
1116   if ((s = strchr(aconf->host, '@')))
1117     s++;
1118   else
1119     s = aconf->host;
1120   /*
1121    * Do name lookup now on hostnames given and store the
1122    * ip numbers in conf structure.
1123    */
1124   if (!isAlpha(*s) && !isDigit(*s))
1125     goto badlookup;
1126
1127   /*
1128    * Prepare structure in case we have to wait for a
1129    * reply which we get later and store away.
1130    */
1131   ln.value.aconf = aconf;
1132   ln.flags = ASYNC_CONF;
1133
1134   if (isDigit(*s))
1135     aconf->ipnum.s_addr = inet_addr(s);
1136   else if ((hp = gethost_byname(s, &ln)))
1137     memcpy(&(aconf->ipnum), hp->h_addr, sizeof(struct in_addr));
1138
1139   if (aconf->ipnum.s_addr == INADDR_NONE)
1140     goto badlookup;
1141   return 0;
1142 badlookup:
1143   if (aconf->ipnum.s_addr == INADDR_NONE)
1144     memset(&aconf->ipnum, 0, sizeof(struct in_addr));
1145   Debug((DEBUG_ERROR, "Host/server name error: (%s) (%s)",
1146       aconf->host, aconf->name));
1147   return -1;
1148 }
1149
1150 /* read_tlines 
1151  * Read info from T:lines into trecords which include the file 
1152  * timestamp, the hostmask, and the contents of the motd file 
1153  * -Ghostwolf 7sep97
1154  */
1155 void read_tlines()
1156 {
1157   aConfItem *tmp;
1158   atrecord *temp, *last = NULL; /* Init. to avoid compiler warning */
1159   aMotdItem *amotd;
1160
1161   /* Free the old trecords and the associated motd contents first */
1162   while (tdata)
1163   {
1164     last = tdata->next;
1165     while (tdata->tmotd)
1166     {
1167       amotd = tdata->tmotd->next;
1168       RunFree(tdata->tmotd);
1169       tdata->tmotd = amotd;
1170     }
1171     RunFree(tdata);
1172     tdata = last;
1173   }
1174
1175   for (tmp = conf; tmp; tmp = tmp->next)
1176     if (tmp->status == CONF_TLINES && tmp->host && tmp->passwd)
1177     {
1178       temp = (atrecord *) RunMalloc(sizeof(atrecord));
1179       if (!temp)
1180         outofmemory();
1181       temp->hostmask = tmp->host;
1182       temp->tmotd = read_motd(tmp->passwd);
1183       temp->tmotd_tm = motd_tm;
1184       temp->next = NULL;
1185       if (!tdata)
1186         tdata = temp;
1187       else
1188         last->next = temp;
1189       last = temp;
1190     }
1191 }
1192
1193 int find_kill(aClient *cptr)
1194 {
1195   char reply[256], *host, *name;
1196   aConfItem *tmp;
1197   aGline *agline = NULL;
1198
1199   if (!cptr->user)
1200     return 0;
1201
1202   host = cptr->sockhost;
1203   name = cptr->user->username;
1204
1205   if (strlen(host) > (size_t)HOSTLEN ||
1206       (name ? strlen(name) : 0) > (size_t)HOSTLEN)
1207     return (0);
1208
1209   reply[0] = '\0';
1210
1211   for (tmp = conf; tmp; tmp = tmp->next)
1212     /* Added a check against the user's IP address as well.
1213      * If the line is either CONF_KILL or CONF_IPKILL, check it; if and only
1214      * if it's CONF_IPKILL, check the IP address as well (the && below will
1215      * short circuit and the match won't even get run) -Kev
1216      */
1217     if ((tmp->status & CONF_KLINE) && tmp->host && tmp->name &&
1218         (match(tmp->host, host) == 0 ||
1219         ((tmp->status == CONF_IPKILL) &&
1220         match(tmp->host, inetntoa(cptr->ip)) == 0)) &&
1221         (!name || match(tmp->name, name) == 0) &&
1222         (!tmp->port || (tmp->port == cptr->acpt->port)))
1223     {
1224       /*
1225        * Can short-circuit evaluation - not taking chances
1226        * because check_time_interval destroys tmp->passwd
1227        * - Mmmm
1228        */
1229       if (BadPtr(tmp->passwd))
1230         break;
1231       else if (is_comment(tmp->passwd))
1232         break;
1233       else if (check_time_interval(tmp->passwd, reply))
1234         break;
1235     }
1236
1237   if (reply[0])
1238     sendto_one(cptr, reply, me.name, ERR_YOUREBANNEDCREEP, cptr->name);
1239   else if (tmp)
1240   {
1241     if (BadPtr(tmp->passwd))
1242       sendto_one(cptr,
1243           ":%s %d %s :Connection from your host is refused on this server.",
1244           me.name, ERR_YOUREBANNEDCREEP, cptr->name);
1245     else
1246     {
1247       if (*tmp->passwd == '"')
1248       {
1249         char *sbuf =
1250             sprintf_irc(sendbuf, ":%s %d %s :%s", me.name, ERR_YOUREBANNEDCREEP,
1251             cptr->name, &tmp->passwd[1]);
1252         sbuf[-1] = '.';         /* Overwrite last quote with a dot */
1253         sendbufto_one(cptr);
1254       }
1255       else if (*tmp->passwd == '!')
1256         killcomment(cptr, cptr->name, &tmp->passwd[1]);
1257       else
1258 #ifdef COMMENT_IS_FILE
1259         killcomment(cptr, cptr->name, tmp->passwd);
1260 #else
1261         sendto_one(cptr, ":%s %d %s :%s.", me.name, ERR_YOUREBANNEDCREEP,
1262             cptr->name, tmp->passwd);
1263 #endif
1264     }
1265   }
1266
1267   /* find active glines */
1268   /* added a check against the user's IP address to find_gline() -Kev */
1269   else if ((agline = find_gline(cptr, NULL)) && GlineIsActive(agline))
1270     sendto_one(cptr, ":%s %d %s :%s.", me.name, ERR_YOUREBANNEDCREEP,
1271         cptr->name, agline->reason);
1272   else
1273     agline = NULL;              /* if a gline was found, it was inactive */
1274
1275   return (tmp ? -1 : (agline ? -2 : 0));
1276 }
1277
1278 #ifdef R_LINES
1279 /*
1280  * find_restrict
1281  *
1282  * Works against host/name and calls an outside program
1283  * to determine whether a client is allowed to connect.  This allows
1284  * more freedom to determine who is legal and who isn't, for example
1285  * machine load considerations.  The outside program is expected to
1286  * return a reply line where the first word is either 'Y' or 'N' meaning
1287  * "Yes Let them in" or "No don't let them in."  If the first word
1288  * begins with neither 'Y' or 'N' the default is to let the person on.
1289  * It returns a value of 0 if the user is to be let through -Hoppie
1290  */
1291 int find_restrict(aClient *cptr)
1292 {
1293   aConfItem *tmp;
1294   char reply[80], temprpl[80];
1295   char *rplhold = reply, *host, *name, *s;
1296   char rplchar = 'Y';
1297   int pi[2], rc = 0, n;
1298   FBFILE *file = NULL;
1299
1300   if (!cptr->user)
1301     return 0;
1302   name = cptr->user->username;
1303   host = cptr->sockhost;
1304   Debug((DEBUG_INFO, "R-line check for %s[%s]", name, host));
1305
1306   for (tmp = conf; tmp; tmp = tmp->next)
1307   {
1308     if (tmp->status != CONF_RESTRICT ||
1309         (tmp->host && host && match(tmp->host, host)) ||
1310         (tmp->name && name && match(tmp->name, name)))
1311       continue;
1312
1313     if (BadPtr(tmp->passwd))
1314     {
1315       sendto_ops("Program missing on R-line %s/%s, ignoring", name, host);
1316       continue;
1317     }
1318
1319     if (pipe(pi) == -1)
1320     {
1321       report_error("Error creating pipe for R-line %s: %s", &me);
1322       return 0;
1323     }
1324     switch (rc = fork())
1325     {
1326       case -1:
1327         report_error("Error forking for R-line %s: %s", &me);
1328         return 0;
1329       case 0:
1330       {
1331         Reg1 int i;
1332
1333         close(pi[0]);
1334         for (i = 2; i < MAXCONNECTIONS; i++)
1335           if (i != pi[1])
1336             close(i);
1337         if (pi[1] != 2)
1338           dup2(pi[1], 2);
1339         dup2(2, 1);
1340         if (pi[1] != 2 && pi[1] != 1)
1341           close(pi[1]);
1342         execlp(tmp->passwd, tmp->passwd, name, host, 0);
1343         exit(-1);
1344       }
1345       default:
1346         close(pi[1]);
1347         break;
1348     }
1349     *reply = '\0';
1350     file = fdbopen(pi[0], "r");
1351     while (fbgets(temprpl, sizeof(temprpl) - 1, file))
1352     {
1353       if ((s = strchr(temprpl, '\n')))
1354         *s = '\0';
1355       if (strlen(temprpl) + strlen(reply) < sizeof(reply) - 2)
1356         sprintf_irc(rplhold, "%s %s", rplhold, temprpl);
1357       else
1358       {
1359         sendto_ops("R-line %s/%s: reply too long!", name, host);
1360         break;
1361       }
1362     }
1363     fbclose(file);
1364     kill(rc, SIGKILL);          /* cleanup time */
1365     wait(0);
1366
1367     rc = 0;
1368     while (*rplhold == ' ')
1369       rplhold++;
1370     rplchar = *rplhold;         /* Pull out the yes or no */
1371     while (*rplhold != ' ')
1372       rplhold++;
1373     while (*rplhold == ' ')
1374       rplhold++;
1375     strcpy(reply, rplhold);
1376     rplhold = reply;
1377
1378     if ((rc = (rplchar == 'n' || rplchar == 'N')))
1379       break;
1380   }
1381   if (rc)
1382   {
1383     sendto_one(cptr, ":%s %d %s :Restriction: %s",
1384         me.name, ERR_YOUREBANNEDCREEP, cptr->name, reply);
1385     return -1;
1386   }
1387   return 0;
1388 }
1389 #endif
1390
1391 /*
1392  * output the reason for being k lined from a file  - Mmmm
1393  * sptr is server
1394  * parv is the sender prefix
1395  * filename is the file that is to be output to the K lined client
1396  */
1397 static void killcomment(aClient *sptr, char *parv, char *filename)
1398 {
1399   FBFILE *file = NULL;
1400   char line[80];
1401   Reg1 char *tmp;
1402   struct stat sb;
1403   struct tm *tm;
1404
1405   if (NULL == (file = fbopen(filename, "r")))
1406   {
1407     sendto_one(sptr, err_str(ERR_NOMOTD), me.name, parv);
1408     sendto_one(sptr,
1409         ":%s %d %s :Connection from your host is refused on this server.",
1410         me.name, ERR_YOUREBANNEDCREEP, parv);
1411     return;
1412   }
1413   fbstat(&sb, file);
1414   tm = localtime((time_t *) & sb.st_mtime);     /* NetBSD needs cast */
1415   while (fbgets(line, sizeof(line) - 1, file))
1416   {
1417     if ((tmp = strchr(line, '\n')))
1418       *tmp = '\0';
1419     if ((tmp = strchr(line, '\r')))
1420       *tmp = '\0';
1421     /* sendto_one(sptr,
1422      * ":%s %d %s : %s.",
1423      * me.name, ERR_YOUREBANNEDCREEP, parv,line); */
1424     sendto_one(sptr, rpl_str(RPL_MOTD), me.name, parv, line);
1425   }
1426   sendto_one(sptr,
1427       ":%s %d %s :Connection from your host is refused on this server.",
1428       me.name, ERR_YOUREBANNEDCREEP, parv);
1429   fbclose(file);
1430   return;
1431 }
1432
1433 /*
1434  *  is the K line field an interval or a comment? - Mmmm
1435  */
1436 static int is_comment(char *comment)
1437 {
1438   size_t i;
1439   for (i = 0; i < strlen(comment); i++)
1440     if ((comment[i] != ' ') && (comment[i] != '-')
1441         && (comment[i] != ',') && ((comment[i] < '0') || (comment[i] > '9')))
1442       return (1);
1443
1444   return (0);
1445 }
1446
1447 /*
1448  *  check against a set of time intervals
1449  */
1450 static int check_time_interval(char *interval, char *reply)
1451 {
1452   struct tm *tptr;
1453   char *p;
1454   int perm_min_hours, perm_min_minutes, perm_max_hours, perm_max_minutes;
1455   int nowm, perm_min, perm_max;
1456
1457   tptr = localtime(&now);
1458   nowm = tptr->tm_hour * 60 + tptr->tm_min;
1459
1460   while (interval)
1461   {
1462     p = strchr(interval, ',');
1463     if (p)
1464       *p = '\0';
1465     if (sscanf(interval, "%2d%2d-%2d%2d", &perm_min_hours, &perm_min_minutes,
1466         &perm_max_hours, &perm_max_minutes) != 4)
1467     {
1468       if (p)
1469         *p = ',';
1470       return (0);
1471     }
1472     if (p)
1473       *(p++) = ',';
1474     perm_min = 60 * perm_min_hours + perm_min_minutes;
1475     perm_max = 60 * perm_max_hours + perm_max_minutes;
1476     /*
1477      * The following check allows intervals over midnight ...
1478      */
1479     if ((perm_min < perm_max)
1480         ? (perm_min <= nowm && nowm <= perm_max)
1481         : (perm_min <= nowm || nowm <= perm_max))
1482     {
1483       printf(reply,
1484           ":%%s %%d %%s :%s %d:%02d to %d:%02d.",
1485           "You are not allowed to connect from",
1486           perm_min_hours, perm_min_minutes, perm_max_hours, perm_max_minutes);
1487       return (ERR_YOUREBANNEDCREEP);
1488     }
1489     if ((perm_min < perm_max)
1490         ? (perm_min <= nowm + 5 && nowm + 5 <= perm_max)
1491         : (perm_min <= nowm + 5 || nowm + 5 <= perm_max))
1492     {
1493       sprintf_irc(reply, ":%%s %%d %%s :%d minute%s%s",
1494           perm_min - nowm, (perm_min - nowm) > 1 ? "s " : " ",
1495           "and you will be denied for further access");
1496       return (ERR_YOUWILLBEBANNED);
1497     }
1498     interval = p;
1499   }
1500   return (0);
1501 }
1502
1503 aMotdItem *read_motd(char *motdfile)
1504 {
1505   FBFILE *file = NULL;
1506   register aMotdItem *temp, *newmotd, *last;
1507   struct stat sb;
1508   char line[80];
1509   register char *tmp;
1510
1511   if (NULL == (file = fbopen(motdfile, "r")))
1512   {
1513     Debug((DEBUG_ERROR, "Couldn't open \"%s\": %s", motdfile, strerror(errno)));
1514     return NULL;
1515   }
1516   if (-1 == fbstat(&sb, file))
1517   {
1518     return NULL;
1519   }
1520   newmotd = last = NULL;
1521   motd_tm = *localtime((time_t *) & sb.st_mtime);       /* NetBSD needs cast */
1522   while (fbgets(line, sizeof(line) - 1, file))
1523   {
1524     if ((tmp = (char *)strchr(line, '\n')))
1525       *tmp = '\0';
1526     if ((tmp = (char *)strchr(line, '\r')))
1527       *tmp = '\0';
1528     temp = (aMotdItem *) RunMalloc(sizeof(aMotdItem));
1529     if (!temp)
1530       outofmemory();
1531     strcpy(temp->line, line);
1532     temp->next = NULL;
1533     if (!newmotd)
1534       newmotd = temp;
1535     else
1536       last->next = temp;
1537     last = temp;
1538   }
1539   fbclose(file);
1540   return newmotd;
1541 }