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