1 /*
2  * misc.c -- handles:
3  *   split() maskhost() dumplots() daysago() days() daysdur()
4  *   logging things
5  *   queueing output for the bot (msg and help)
6  *   resync buffers for sharebots
7  *   help system
8  *   motd display and %var substitution
9  */
10 /*
11  * Copyright (C) 1997 Robey Pointer
12  * Copyright (C) 1999 - 2021 Eggheads Development Team
13  *
14  * This program is free software; you can redistribute it and/or
15  * modify it under the terms of the GNU General Public License
16  * as published by the Free Software Foundation; either version 2
17  * of the License, or (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
27  */
28 
29 #include "main.h"
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include "chan.h"
34 #include "tandem.h"
35 #include "modules.h"
36 
37 #include <sys/utsname.h>
38 
39 #include "stat.h"
40 
41 extern struct dcc_t *dcc;
42 extern struct chanset_t *chanset;
43 
44 extern char helpdir[], version[], origbotname[], botname[], admin[], network[],
45             motdfile[], ver[], botnetnick[], bannerfile[], textdir[];
46 extern int  backgrd, con_chan, term_z, use_stderr, dcc_total, keep_all_logs,
47             quick_logs;
48 
49 extern time_t now;
50 extern Tcl_Interp *interp;
51 
52 char logfile_suffix[21] = ".%d%b%Y";    /* Format of logfile suffix */
53 char log_ts[33] = "[%H:%M:%S]"; /* Timestamp format for logfile entries */
54 
55 int shtime = 1;                 /* Display the time with console output */
56 log_t *logs = 0;                /* Logfiles */
57 int max_logs = 5;               /* Max log files, mismatch config on purpose */
58 int max_logsize = 0;            /* Maximum logfile size, 0 for no limit */
59 int raw_log = 0;                /* Display output to server to LOG_SERVEROUT */
60 int conmask = LOG_MODES | LOG_CMDS | LOG_MISC; /* Console mask */
61 int show_uname = 1;
62 
63 struct help_list_t {
64   struct help_list_t *next;
65   char *name;
66   int type;
67 };
68 
69 static struct help_ref {
70   char *name;
71   struct help_list_t *first;
72   struct help_ref *next;
73 } *help_list = NULL;
74 
75 
76 /* Expected memory usage
77  */
expmem_misc()78 int expmem_misc()
79 {
80   struct help_ref *current;
81   struct help_list_t *item;
82   int tot = 0;
83 
84   for (current = help_list; current; current = current->next) {
85     tot += sizeof(struct help_ref) + strlen(current->name) + 1;
86 
87     for (item = current->first; item; item = item->next)
88       tot += sizeof(struct help_list_t) + strlen(item->name) + 1;
89   }
90   return tot + (max_logs * sizeof(log_t));
91 }
92 
init_misc()93 void init_misc()
94 {
95   static int last = 0;
96 
97   if (max_logs < 1)
98     max_logs = 1;
99   if (logs)
100     logs = nrealloc(logs, max_logs * sizeof(log_t));
101   else
102     logs = nmalloc(max_logs * sizeof(log_t));
103   for (; last < max_logs; last++) {
104     logs[last].filename = logs[last].chname = NULL;
105     logs[last].mask = 0;
106     logs[last].f = NULL;
107     /* Added by cybah  */
108     logs[last].szlast[0] = 0;
109     logs[last].repeats = 0;
110     /* Added by rtc  */
111     logs[last].flags = 0;
112   }
113 }
114 
115 
116 /*
117  *    Misc functions
118  */
119 
120 /* low-level stuff for other modules
121  */
is_file(const char * s)122 int is_file(const char *s)
123 {
124   struct stat ss;
125   int i = stat(s, &ss);
126 
127   if (i < 0)
128     return 0;
129   if ((ss.st_mode & S_IFREG) || (ss.st_mode & S_IFLNK))
130     return 1;
131   return 0;
132 }
133 
134 /*  This implementation wont overrun dst - 'max' is the max bytes that dst
135  *  can be, including the null terminator. So if 'dst' is a 128 byte buffer,
136  *  pass 128 as 'max'. The function will _always_ null-terminate 'dst'.
137  *
138  *  Returns: The number of characters appended to 'dst'.
139  *
140  *  Usage example:
141  *
142  *    char buf[128];
143  *    size_t bufsize = sizeof(buf);
144  *
145  *    buf[0] = 0, bufsize--;
146  *
147  *    while (blah && bufsize) {
148  *      bufsize -= egg_strcatn(buf, <some-long-string>, sizeof(buf));
149  *    }
150  *
151  *  <Cybah>
152  */
egg_strcatn(char * dst,const char * src,size_t max)153 int egg_strcatn(char *dst, const char *src, size_t max)
154 {
155   size_t tmpmax = 0;
156 
157   /* find end of 'dst' */
158   while (*dst && max > 0) {
159     dst++;
160     max--;
161   }
162 
163   /*    Store 'max', so we can use it to workout how many characters were
164    *  written later on.
165    */
166   tmpmax = max;
167 
168   /* copy up to, but not including the null terminator */
169   while (*src && max > 1) {
170     *dst++ = *src++;
171     max--;
172   }
173 
174   /* null-terminate the buffer */
175   *dst = 0;
176 
177   /*    Don't include the terminating null in our count, as it will cumulate
178    *  in loops - causing a headache for the caller.
179    */
180   return tmpmax - max;
181 }
182 
my_strcpy(char * a,char * b)183 int my_strcpy(char *a, char *b)
184 {
185   char *c = b;
186 
187   while (*b)
188     *a++ = *b++;
189   *a = *b;
190   return b - c;
191 }
192 
193 /* Split first word off of rest and put it in first
194  */
splitc(char * first,char * rest,char divider)195 void splitc(char *first, char *rest, char divider)
196 {
197   char *p = strchr(rest, divider);
198 
199   if (p == NULL) {
200     if (first != rest && first)
201       first[0] = 0;
202     return;
203   }
204   *p = 0;
205   if (first != NULL)
206     strcpy(first, rest);
207   if (first != rest)
208     memmove(rest, p + 1, strlen(p + 1) + 1);
209 }
210 
211 /*    As above, but lets you specify the 'max' number of bytes (EXCLUDING the
212  * terminating null).
213  *
214  * Example of use:
215  *
216  * char buf[HANDLEN + 1];
217  *
218  * splitcn(buf, input, "@", HANDLEN);
219  *
220  * <Cybah>
221  */
splitcn(char * first,char * rest,char divider,size_t max)222 void splitcn(char *first, char *rest, char divider, size_t max)
223 {
224   char *p = strchr(rest, divider);
225 
226   if (p == NULL) {
227     if (first != rest && first)
228       first[0] = 0;
229     return;
230   }
231   *p = 0;
232   if (first != NULL)
233     strlcpy(first, rest, max);
234   if (first != rest)
235     /*    In most circumstances, strcpy with src and dst being the same buffer
236      *  can produce undefined results. We're safe here, as the src is
237      *  guaranteed to be at least 2 bytes higher in memory than dest. <Cybah>
238      */
239     strcpy(rest, p + 1);
240 }
241 
splitnick(char ** blah)242 char *splitnick(char **blah)
243 {
244   char *p = strchr(*blah, '!'), *q = *blah;
245 
246   if (p) {
247     *p = 0;
248     *blah = p + 1;
249     return q;
250   }
251   return "";
252 }
253 
remove_crlf(char ** line)254 void remove_crlf(char **line)
255 {
256   char *p = *line;
257 
258   while (*p) {
259     if (*p == '\r' || *p == '\n')
260     {
261       *p = 0;
262       break;
263     }
264     p++;
265   }
266 }
267 
newsplit(char ** rest)268 char *newsplit(char **rest)
269 {
270   char *o, *r;
271 
272   if (!rest)
273     return "";
274   o = *rest;
275   while (*o == ' ')
276     o++;
277   r = o;
278   while (*o && (*o != ' '))
279     o++;
280   if (*o)
281     *o++ = 0;
282   *rest = o;
283   return r;
284 }
285 
286 /* maskhost(), modified to support custom mask types, as defined
287  * by mIRC.
288  * Does not require a proper hostmask in 's'. Accepts any strings,
289  * including empty ones and attempts to provide meaningful results.
290  *
291  * Strings containing no '@' character will be parsed as if the
292  * whole string is a host.
293  * Strings containing no '!' character will be interpreted as if
294  * there is no nick.
295  * '!' as a nick/user separator must precede any '@' characters.
296  * Otherwise it will be considered a part of the host.
297  * Supported types are listed in tcl-commands.doc in the maskhost
298  * command section. Type 3 resembles the older maskhost() most closely.
299  *
300  * Specific examples (with type=3):
301  *
302  * "nick!user@is.the.lamest.bg"  -> *!*user@*.the.lamest.bg (ccTLD)
303  * "nick!user@is.the.lamest.com" -> *!*user@*.lamest.com (gTLD)
304  * "lamest.example"              -> *!*@lamest.example
305  * "whatever@lamest.example"     -> *!*whatever@lamest.example
306  * "com.example@user!nick"       -> *!*com.example@user!nick
307  * "!"                           -> *!*@!
308  * "@"                           -> *!*@*
309  * ""                            -> *!*@*
310  * "abc!user@2001:db8:618:5c0:263:15:dead:babe"
311  * -> *!*user@2001:db8:618:5c0:263:15:dead:*
312  * "abc!user@0:0:0:0:0:ffff:1.2.3.4"
313  * -> *!*user@0:0:0:0:0:ffff:1.2.3.*
314  */
maskaddr(const char * s,char * nw,int type)315 void maskaddr(const char *s, char *nw, int type)
316 {
317   int d = type % 5, num = 1;
318   char *p, *u = 0, *h = 0, *ss;
319 
320   /* Look for user and host.. */
321   ss = (char *)s;
322   u = strchr(s, '!');
323   if (u)
324     h = strchr(u, '@');
325   if (!h){
326     h = strchr(s, '@');
327     u = 0;
328   }
329 
330   /* Print nick if required and available */
331   if (!u || (type % 10) < 5)
332     *nw++ = '*';
333   else {
334     strncpy(nw, s, u - s);
335     nw += u - s;
336   }
337   *nw++ = '!';
338 
339   /* Write user if required and available */
340   u = (u ? u + 1 : ss);
341   if (!h || (d == 2) || (d == 4))
342     *nw++ = '*';
343   else {
344     if (d) {
345       *nw++ = '*';
346       if (strchr("~+-^=", *u))
347         u++; /* trim leading crap */
348     }
349     strncpy(nw, u, h - u);
350     nw += h - u;
351   }
352   *nw++ = '@';
353 
354   if (type >= 30) {
355     strcpy(nw, "*");
356     return;
357   }
358 
359   /* The rest is for the host */
360   h = (h ? h + 1 : ss);
361   for (p = h; *p; p++) /* hostname? */
362     if ((*p > '9' || *p < '0') && *p != '.') {
363       num = 0;
364       break;
365     }
366   p = strrchr(h, ':'); /* IPv6? */
367   /* Mask out after the last colon/dot */
368   if (p && d > 2) {
369     if ((u = strrchr(p, '.')))
370       p = u;
371     strncpy(nw, h, ++p - h);
372     nw += p - h;
373     strcpy(nw, "*");
374   } else if (!p && !num && type >= 10) {
375       /* we have a hostname and type
376        requires us to replace numbers */
377     num = 0;
378     for (p = h; *p; p++) {
379       if (*p < '0' || *p > '9') {
380         *nw++ = *p;
381         num = 0;
382       } else {
383         if (type < 20)
384           *nw++ = '?';
385         else if (!num) {
386           *nw++ = '*';
387           num = 1; /* place only one '*'
388                       per numeric sequence */
389         }
390       }
391     }
392     *nw = 0;
393   } else if (d > 2 && (p = strrchr(h, '.'))) {
394     if (num) { /* IPv4 */
395       strncpy(nw, h, p - h);
396       nw += p - h;
397       strcpy(nw, ".*");
398       return;
399     }
400     for (u = h, d = 0; (u = strchr(++u, '.')); d++);
401     if (d < 2) { /* types < 2 don't mask the host */
402       strcpy(nw, h);
403       return;
404     }
405     u = strchr(h, '.');
406     if (d > 3 || (d == 3 && strlen(p) > 3))
407       u = strchr(++u, '.'); /* ccTLD or not? Look above. */
408     sprintf(nw, "*%s", u);
409   } else if (!*h)
410       /* take care if the mask is empty or contains only '@' */
411       strcpy(nw, "*");
412     else
413       strcpy(nw, h);
414 }
415 
416 /* Dump a potentially super-long string of text.
417  */
dumplots(int idx,const char * prefix,const char * data)418 void dumplots(int idx, const char *prefix, const char *data)
419 {
420   const char *p = data, *q, *n;
421   const int max_data_len = 500 - strlen(prefix);
422 
423   if (!*data) {
424     dprintf(idx, "%s\n", prefix);
425     return;
426   }
427   while (strlen(p) > max_data_len) {
428     q = p + max_data_len;
429     /* Search for embedded linefeed first */
430     n = strchr(p, '\n');
431     if (n && n < q) {
432       /* Great! dump that first line then start over */
433       dprintf(idx, "%s%.*s\n", prefix, n - p, p);
434       p = n + 1;
435     } else {
436       /* Search backwards for the last space */
437       while (*q != ' ' && q != p)
438         q--;
439       if (q == p)
440         q = p + max_data_len;
441       dprintf(idx, "%s%.*s\n", prefix, q - p, p);
442       p = q;
443       if (*q == ' ')
444         p++;
445     }
446   }
447   /* Last trailing bit: split by linefeeds if possible */
448   n = strchr(p, '\n');
449   while (n) {
450     dprintf(idx, "%s%.*s\n", prefix, n - p, p);
451     p = n + 1;
452     n = strchr(p, '\n');
453   }
454   if (*p)
455     dprintf(idx, "%s%s\n", prefix, p);  /* Last trailing bit */
456 }
457 
458 /* Convert an interval (in seconds) to one of:
459  * "19 days ago", "1 day ago", "18:12"
460  */
daysago(time_t now,time_t then,char * out)461 void daysago(time_t now, time_t then, char *out)
462 {
463   if (now - then > 86400) {
464     int days = (now - then) / 86400;
465 
466     sprintf(out, "%d day%s ago", days, (days == 1) ? "" : "s");
467     return;
468   }
469   strftime(out, 6, "%H:%M", localtime(&then));
470 }
471 
472 /* Convert an interval (in seconds) to one of:
473  * "in 19 days", "in 1 day", "at 18:12"
474  */
days(time_t now,time_t then,char * out)475 void days(time_t now, time_t then, char *out)
476 {
477   if (now - then > 86400) {
478     int days = (now - then) / 86400;
479 
480     sprintf(out, "in %d day%s", days, (days == 1) ? "" : "s");
481     return;
482   }
483   strftime(out, 9, "at %H:%M", localtime(&now));
484 }
485 
486 /* Convert an interval (in seconds) to one of:
487  * "for 19 days", "for 1 day", "for 09:10"
488  */
daysdur(time_t now,time_t then,char * out)489 void daysdur(time_t now, time_t then, char *out)
490 {
491   char s[81];
492   int hrs, mins;
493 
494   if (now - then > 86400) {
495     int days = (now - then) / 86400;
496 
497     sprintf(out, "for %d day%s", days, (days == 1) ? "" : "s");
498     return;
499   }
500   strcpy(out, "for ");
501   now -= then;
502   hrs = (int) (now / 3600);
503   mins = (int) ((now - (hrs * 3600)) / 60);
504   sprintf(s, "%02d:%02d", hrs, mins);
505   strcat(out, s);
506 }
507 
508 
509 /*
510  *    Logging functions
511  */
512 
513 /* Log something
514  * putlog(level,channel_name,format,...);
515  */
EGG_VARARGS_DEF(int,arg1)516 void putlog EGG_VARARGS_DEF(int, arg1)
517 {
518   static int inhere = 0;
519   int i, type, tsl = 0;
520   char *format, *chname, s[LOGLINELEN], s1[LOGLINELEN], *out, ct[81], *s2, stamp[34];
521   va_list va;
522   time_t now2 = time(NULL);
523   static time_t now2_last = 0; /* cache expensive localtime() */
524   static struct tm *t;
525 
526   if (now2 != now2_last) {
527     now2_last = now2;
528     t = localtime(&now2);
529   }
530 
531   type = EGG_VARARGS_START(int, arg1, va);
532   chname = va_arg(va, char *);
533   format = va_arg(va, char *);
534 
535   /* Create the timestamp */
536   if (shtime) {
537     strftime(stamp, sizeof(stamp) - 2, log_ts, t);
538     strcat(stamp, " ");
539     tsl = strlen(stamp);
540   }
541   else
542     *stamp = '\0';
543 
544   /* Format log entry at offset 'tsl,' then i can prepend the timestamp */
545   out = s + tsl;
546   /* No need to check if out should be null-terminated here,
547    * just do it! <cybah>
548    */
549   egg_vsnprintf(out, LOGLINEMAX - tsl, format, va);
550   out[LOGLINEMAX - tsl] = 0;
551   if (keep_all_logs) {
552     if (!logfile_suffix[0])
553       strftime(ct, 12, ".%d%b%Y", t);
554     else {
555       strftime(ct, 80, logfile_suffix, t);
556       ct[80] = 0;
557       s2 = ct;
558       /* replace spaces by underscores */
559       while (s2[0]) {
560         if (s2[0] == ' ')
561           s2[0] = '_';
562         s2++;
563       }
564     }
565   }
566   /* Make sure the bind list is initialized and we're not looping here */
567   if (!inhere && H_log) {
568     inhere = 1;
569     check_tcl_log(type, chname, out);
570     inhere = 0;
571   }
572   /* Place the timestamp in the string to be printed */
573   if (out[0] && shtime) {
574     memcpy(s, stamp, tsl);
575     out = s;
576   }
577   strcat(out, "\n");
578   if (!use_stderr) {
579     for (i = 0; i < max_logs; i++) {
580       if ((logs[i].filename != NULL) && (logs[i].mask & type) &&
581           ((chname[0] == '*') || (logs[i].chname[0] == '*') ||
582            (!rfc_casecmp(chname, logs[i].chname)))) {
583         if (logs[i].f == NULL) {
584           /* Open this logfile */
585           if (keep_all_logs) {
586             egg_snprintf(s1, 256, "%s%s", logs[i].filename, ct);
587             logs[i].f = fopen(s1, "a");
588           } else
589             logs[i].f = fopen(logs[i].filename, "a");
590         }
591         if (logs[i].f != NULL) {
592           /* Check if this is the same as the last line added to
593            * the log. <cybah>
594            */
595           if (!strcasecmp(out + tsl, logs[i].szlast))
596             /* It is a repeat, so increment repeats */
597             logs[i].repeats++;
598           else {
599             /* Not a repeat, check if there were any repeat
600              * lines previously...
601              */
602             if (logs[i].repeats > 0) {
603               /* Yep.. so display 'last message repeated x times'
604                * then reset repeats. We want the current time here,
605                * so put that in the file first.
606                */
607               fprintf(logs[i].f, "%s", stamp);
608               fprintf(logs[i].f, MISC_LOGREPEAT, logs[i].repeats);
609               logs[i].repeats = 0;
610               /* No need to reset logs[i].szlast here
611                * because we update it later on...
612                */
613             }
614             fputs(out, logs[i].f);
615             strlcpy(logs[i].szlast, out + tsl, LOGLINEMAX);
616           }
617         }
618       }
619     }
620   }
621   for (i = 0; i < dcc_total; i++) {
622     if ((dcc[i].type == &DCC_CHAT) && (dcc[i].u.chat->con_flags & type)) {
623       if ((chname[0] == '*') || (dcc[i].u.chat->con_chan[0] == '*') ||
624           !rfc_casecmp(chname, dcc[i].u.chat->con_chan)) {
625         dprintf(i, "%s", out);
626       }
627     }
628   }
629   if (!backgrd && !con_chan && term_z < 0)
630     dprintf(DP_STDOUT, "%s", out);
631   else if ((type & LOG_MISC) && use_stderr) {
632     if (shtime)
633       out += tsl;
634     dprintf(DP_STDERR, "%s", out);
635   }
636   va_end(va);
637 }
638 
639 /* Called as soon as the logfile suffix changes. All logs are closed
640  * and the new suffix is stored in `logfile_suffix'.
641  */
logsuffix_change(char * s)642 void logsuffix_change(char *s)
643 {
644   int i;
645   char *s2 = logfile_suffix;
646 
647   /* If the suffix didn't really change, ignore. It's probably a rehash. */
648   if (!s || (s && s2 && !strcmp(s, s2)))
649     return;
650 
651   debug0("Logfile suffix changed. Closing all open logs.");
652   strlcpy(logfile_suffix, s, sizeof logfile_suffix);
653   while (s2[0]) {
654     if (s2[0] == ' ')
655       s2[0] = '_';
656     s2++;
657   }
658   for (i = 0; i < max_logs; i++) {
659     if (logs[i].f) {
660       fflush(logs[i].f);
661       fclose(logs[i].f);
662       logs[i].f = NULL;
663     }
664   }
665 }
666 
check_logsize()667 void check_logsize()
668 {
669   struct stat ss;
670   int i;
671 
672 /* int x=1; */
673   char buf[1024];               /* Should be plenty */
674 
675   if (!keep_all_logs && max_logsize > 0) {
676     for (i = 0; i < max_logs; i++) {
677       if (logs[i].filename) {
678         if (stat(logs[i].filename, &ss) != 0) {
679           break;
680         }
681         if ((ss.st_size >> 10) > max_logsize) {
682           if (logs[i].f) {
683             /* write to the log before closing it huh.. */
684             putlog(LOG_MISC, "*", MISC_CLOGS, logs[i].filename, ss.st_size);
685             fflush(logs[i].f);
686             fclose(logs[i].f);
687             logs[i].f = NULL;
688           }
689 
690           egg_snprintf(buf, sizeof buf, "%s.yesterday", logs[i].filename);
691           buf[1023] = 0;
692           unlink(buf);
693           movefile(logs[i].filename, buf);
694         }
695       }
696     }
697   }
698 }
699 
700 /* Flush the logfiles to disk
701  */
flushlogs()702 void flushlogs()
703 {
704   int i;
705 
706   /* Logs may not be initialised yet. */
707   if (!logs)
708     return;
709 
710   /* Now also checks to see if there's a repeat message and
711    * displays the 'last message repeated...' stuff too <cybah>
712    */
713   for (i = 0; i < max_logs; i++) {
714     if (logs[i].f != NULL) {
715       if ((logs[i].repeats > 0) && quick_logs) {
716         /* Repeat.. if quicklogs used then display 'last message
717          * repeated x times' and reset repeats.
718          */
719         char stamp[33];
720 
721         strftime(stamp, sizeof(stamp) - 1, log_ts, localtime(&now));
722         fprintf(logs[i].f, "%s ", stamp);
723         fprintf(logs[i].f, MISC_LOGREPEAT, logs[i].repeats);
724         /* Reset repeats */
725         logs[i].repeats = 0;
726       }
727       fflush(logs[i].f);
728     }
729   }
730 }
731 
732 
733 /*
734  *     String substitution functions
735  */
736 
737 static int cols = 0;
738 static int colsofar = 0;
739 static int blind = 0;
740 static int subwidth = 70;
741 static char *colstr = NULL;
742 
743 
744 /* Add string to colstr
745  */
subst_addcol(char * s,char * newcol)746 static void subst_addcol(char *s, char *newcol)
747 {
748   char *p, *q;
749   int i, colwidth;
750 
751   if ((newcol[0]) && (newcol[0] != '\377'))
752     colsofar++;
753   colstr = nrealloc(colstr, strlen(colstr) + strlen(newcol) +
754                     (colstr[0] ? 2 : 1));
755   if ((newcol[0]) && (newcol[0] != '\377')) {
756     if (colstr[0])
757       strcat(colstr, "\377");
758     strcat(colstr, newcol);
759   }
760   if ((colsofar == cols) || ((newcol[0] == '\377') && (colstr[0]))) {
761     colsofar = 0;
762     strcpy(s, "     ");
763     colwidth = (subwidth - 5) / cols;
764     q = colstr;
765     p = strchr(colstr, '\377');
766     while (p != NULL) {
767       *p = 0;
768       strcat(s, q);
769       for (i = strlen(q); i < colwidth; i++)
770         strcat(s, " ");
771       q = p + 1;
772       p = strchr(q, '\377');
773     }
774     strcat(s, q);
775     nfree(colstr);
776     colstr = nmalloc(1);
777     colstr[0] = 0;
778   }
779 }
780 
egg_uname()781 char *egg_uname()
782 {
783   struct utsname u;
784   static char sysrel[(sizeof u.sysname) + (sizeof u.release)];
785 
786   if (show_uname) {
787     if (uname(&u) < 0)
788       return "*unknown*";
789     else {
790       snprintf(sysrel, sizeof sysrel, "%s %s", u.sysname, u.release);
791       return sysrel;
792     }
793   }
794   else
795     return "";
796 }
797 
798 /* Substitute %x codes in help files
799  *
800  * %B = bot nickname
801  * %V = version
802  * %C = list of channels i monitor
803  * %E = eggdrop banner
804  * %A = admin line
805  * %n = network name
806  * %T = current time ("14:15")
807  * %N = user's nickname
808  * %U = display system name if possible
809  * %{+xy}     require flags to read this section
810  * %{-}       turn of required flag matching only
811  * %{center}  center this line
812  * %{cols=N}  start of columnated section (indented)
813  * %{help=TOPIC} start a section for a particular command
814  * %{end}     end of section
815  */
816 #define HELP_BUF_LEN 256
817 #define HELP_BOLD  1
818 #define HELP_REV   2
819 #define HELP_UNDER 4
820 #define HELP_FLASH 8
821 
help_subst(char * s,char * nick,struct flag_record * flags,int isdcc,char * topic)822 void help_subst(char *s, char *nick, struct flag_record *flags,
823                 int isdcc, char *topic)
824 {
825   struct chanset_t *chan;
826   int i, j, center = 0;
827   static int help_flags;
828   char xx[HELP_BUF_LEN + 1], *current, *q, chr, *writeidx, *readidx, *towrite,
829        sub[512];
830 
831   if (s == NULL) {
832     /* Used to reset substitutions */
833     blind = 0;
834     cols = 0;
835     subwidth = 70;
836     if (colstr != NULL) {
837       nfree(colstr);
838       colstr = NULL;
839     }
840     help_flags = isdcc;
841     return;
842   }
843   strlcpy(xx, s, sizeof xx);
844   readidx = xx;
845   writeidx = s;
846   current = strchr(readidx, '%');
847   while (current) {
848     /* Are we about to copy a chuck to the end of the buffer?
849      * if so return
850      */
851     if ((writeidx + (current - readidx)) >= (s + HELP_BUF_LEN)) {
852       strncpy(writeidx, readidx, (s + HELP_BUF_LEN) - writeidx);
853       s[HELP_BUF_LEN] = 0;
854       return;
855     }
856     chr = *(current + 1);
857     *current = 0;
858     if (!blind)
859       writeidx += my_strcpy(writeidx, readidx);
860     towrite = NULL;
861     switch (chr) {
862     case 'b':
863       if (glob_hilite(*flags)) {
864         if (help_flags & HELP_IRC) {
865           towrite = "\002";
866         } else if (help_flags & HELP_BOLD) {
867           help_flags &= ~HELP_BOLD;
868           towrite = "\033[0m";
869         } else {
870           help_flags |= HELP_BOLD;
871           towrite = "\033[1m";
872         }
873       }
874       break;
875     case 'v':
876       if (glob_hilite(*flags)) {
877         if (help_flags & HELP_IRC) {
878           towrite = "\026";
879         } else if (help_flags & HELP_REV) {
880           help_flags &= ~HELP_REV;
881           towrite = "\033[0m";
882         } else {
883           help_flags |= HELP_REV;
884           towrite = "\033[7m";
885         }
886       }
887       break;
888     case '_':
889       if (glob_hilite(*flags)) {
890         if (help_flags & HELP_IRC) {
891           towrite = "\037";
892         } else if (help_flags & HELP_UNDER) {
893           help_flags &= ~HELP_UNDER;
894           towrite = "\033[0m";
895         } else {
896           help_flags |= HELP_UNDER;
897           towrite = "\033[4m";
898         }
899       }
900       break;
901     case 'f':
902       if (glob_hilite(*flags)) {
903         if (help_flags & HELP_FLASH) {
904           if (help_flags & HELP_IRC)
905             towrite = "\002\037";
906           else
907             towrite = "\033[0m";
908           help_flags &= ~HELP_FLASH;
909         } else {
910           help_flags |= HELP_FLASH;
911           if (help_flags & HELP_IRC)
912             towrite = "\037\002";
913           else
914             towrite = "\033[5m";
915         }
916       }
917       break;
918     case 'U':
919       towrite = egg_uname();
920       break;
921     case 'B':
922       towrite = (isdcc ? botnetnick : botname);
923       break;
924     case 'V':
925       towrite = ver;
926       break;
927     case 'E':
928       towrite = version;
929       break;
930     case 'A':
931       towrite = admin;
932       break;
933     case 'n':
934       towrite = network;
935       break;
936     case 'T':
937       strftime(sub, sizeof sub, "%H:%M", localtime(&now));
938       towrite = sub;
939       break;
940     case 'N':
941       towrite = strchr(nick, ':');
942       if (towrite)
943         towrite++;
944       else
945         towrite = nick;
946       break;
947     case 'C':
948       if (!blind)
949         for (chan = chanset; chan; chan = chan->next) {
950           if ((strlen(chan->dname) + writeidx + 2) >= (s + HELP_BUF_LEN)) {
951             strncpy(writeidx, chan->dname, (s + HELP_BUF_LEN) - writeidx);
952             s[HELP_BUF_LEN] = 0;
953             return;
954           }
955           writeidx += my_strcpy(writeidx, chan->dname);
956           if (chan->next) {
957             *writeidx++ = ',';
958             *writeidx++ = ' ';
959           }
960         }
961       break;
962     case '{':
963       q = current;
964       current++;
965       while ((*current != '}') && (*current))
966         current++;
967       if (*current) {
968         *current = 0;
969         current--;
970         q += 2;
971         /* Now q is the string and p is where the rest of the fcn expects */
972         if (!strncmp(q, "help=", 5)) {
973           if (topic && strcasecmp(q + 5, topic))
974             blind |= 2;
975           else
976             blind &= ~2;
977         } else if (!(blind & 2)) {
978           if (q[0] == '+') {
979             struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };
980 
981             break_down_flags(q + 1, &fr, NULL);
982 
983             /* We used to check flagrec_ok(), but we can use flagrec_eq()
984              * instead because lower flags are automatically added now.
985              */
986             if (!flagrec_eq(&fr, flags))
987               blind |= 1;
988             else
989               blind &= ~1;
990           } else if (q[0] == '-')
991             blind &= ~1;
992           else if (!strcasecmp(q, "end")) {
993             blind &= ~1;
994             subwidth = 70;
995             if (cols) {
996               sub[0] = 0;
997               subst_addcol(sub, "\377");
998               nfree(colstr);
999               colstr = NULL;
1000               cols = 0;
1001               towrite = sub;
1002             }
1003           } else if (!strcasecmp(q, "center"))
1004             center = 1;
1005           else if (!strncmp(q, "cols=", 5)) {
1006             char *r;
1007 
1008             cols = atoi(q + 5);
1009             colsofar = 0;
1010             colstr = nmalloc(1);
1011             colstr[0] = 0;
1012             r = strchr(q + 5, '/');
1013             if (r != NULL)
1014               subwidth = atoi(r + 1);
1015           }
1016         }
1017       } else
1018         current = q;            /* no } so ignore */
1019       break;
1020     default:
1021       if (!blind) {
1022         *writeidx++ = chr;
1023         if (writeidx >= (s + HELP_BUF_LEN)) {
1024           *writeidx = 0;
1025           return;
1026         }
1027       }
1028     }
1029     if (towrite && !blind) {
1030       if ((writeidx + strlen(towrite)) >= (s + HELP_BUF_LEN)) {
1031         strncpy(writeidx, towrite, (s + HELP_BUF_LEN) - writeidx);
1032         s[HELP_BUF_LEN] = 0;
1033         return;
1034       }
1035       writeidx += my_strcpy(writeidx, towrite);
1036     }
1037     if (chr) {
1038       readidx = current + 2;
1039       current = strchr(readidx, '%');
1040     } else {
1041       readidx = current + 1;
1042       current = NULL;
1043     }
1044   }
1045   if (!blind) {
1046     i = strlen(readidx);
1047     if (i && ((writeidx + i) >= (s + HELP_BUF_LEN))) {
1048       strncpy(writeidx, readidx, (s + HELP_BUF_LEN) - writeidx);
1049       s[HELP_BUF_LEN] = 0;
1050       return;
1051     }
1052     strcpy(writeidx, readidx);
1053   } else
1054     *writeidx = 0;
1055   if (center) {
1056     strcpy(xx, s);
1057     i = 35 - (strlen(xx) / 2);
1058     if (i > 0) {
1059       s[0] = 0;
1060       for (j = 0; j < i; j++)
1061         s[j] = ' ';
1062       strcpy(s + i, xx);
1063     }
1064   }
1065   if (cols) {
1066     strlcpy(xx, s, sizeof xx);
1067     s[0] = 0;
1068     subst_addcol(s, xx);
1069   }
1070 }
1071 
scan_help_file(struct help_ref * current,char * filename,int type)1072 static void scan_help_file(struct help_ref *current, char *filename, int type)
1073 {
1074   FILE *f;
1075   char s[HELP_BUF_LEN + 1], *p, *q;
1076   struct help_list_t *list;
1077 
1078   if (is_file(filename) && (f = fopen(filename, "r"))) {
1079     /* don't check for feof after fgets, skips last line if it has no \n (ie on windows) */
1080     while (!feof(f) && fgets(s, HELP_BUF_LEN, f) != NULL) {
1081       p = s;
1082       while ((q = strstr(p, "%{help="))) {
1083         q += 7;
1084         if ((p = strchr(q, '}'))) {
1085           *p = 0;
1086           list = nmalloc(sizeof *list);
1087 
1088           list->name = nmalloc(p - q + 1);
1089           strcpy(list->name, q);
1090           list->next = current->first;
1091           list->type = type;
1092           current->first = list;
1093           p++;
1094         } else
1095           p = "";
1096       }
1097     }
1098     /* fgets == NULL means error or empty file, so check for error */
1099     if (ferror(f)) {
1100       putlog(LOG_MISC, "*", "Error reading help file");
1101     }
1102     fclose(f);
1103   }
1104 }
1105 
add_help_reference(char * file)1106 void add_help_reference(char *file)
1107 {
1108   char s[1024];
1109   struct help_ref *current;
1110 
1111   for (current = help_list; current; current = current->next)
1112     if (!strcmp(current->name, file))
1113       return;                   /* Already exists, can't re-add :P */
1114   current = nmalloc(sizeof *current);
1115 
1116   current->name = nmalloc(strlen(file) + 1);
1117   strcpy(current->name, file);
1118   current->next = help_list;
1119   current->first = NULL;
1120   help_list = current;
1121   egg_snprintf(s, sizeof s, "%smsg/%s", helpdir, file);
1122   scan_help_file(current, s, 0);
1123   egg_snprintf(s, sizeof s, "%s%s", helpdir, file);
1124   scan_help_file(current, s, 1);
1125   egg_snprintf(s, sizeof s, "%sset/%s", helpdir, file);
1126   scan_help_file(current, s, 2);
1127 }
1128 
rem_help_reference(char * file)1129 void rem_help_reference(char *file)
1130 {
1131   struct help_ref *current, *last = NULL;
1132   struct help_list_t *item;
1133 
1134   for (current = help_list; current; last = current, current = current->next)
1135     if (!strcmp(current->name, file)) {
1136       while ((item = current->first)) {
1137         current->first = item->next;
1138         nfree(item->name);
1139         nfree(item);
1140       }
1141       nfree(current->name);
1142       if (last)
1143         last->next = current->next;
1144       else
1145         help_list = current->next;
1146       nfree(current);
1147       return;
1148     }
1149 }
1150 
reload_help_data(void)1151 void reload_help_data(void)
1152 {
1153   struct help_ref *current = help_list, *next;
1154   struct help_list_t *item;
1155 
1156   help_list = NULL;
1157   while (current) {
1158     while ((item = current->first)) {
1159       current->first = item->next;
1160       nfree(item->name);
1161       nfree(item);
1162     }
1163     add_help_reference(current->name);
1164     nfree(current->name);
1165     next = current->next;
1166     nfree(current);
1167     current = next;
1168   }
1169 }
1170 
debug_help(int idx)1171 void debug_help(int idx)
1172 {
1173   struct help_ref *current;
1174   struct help_list_t *item;
1175 
1176   for (current = help_list; current; current = current->next) {
1177     dprintf(idx, "HELP FILE(S): %s\n", current->name);
1178     for (item = current->first; item; item = item->next) {
1179       dprintf(idx, "   %s (%s)\n", item->name, (item->type == 0) ? "msg/" :
1180               (item->type == 1) ? "" : "set/");
1181     }
1182   }
1183 }
1184 
resolve_help(int dcc,char * file)1185 FILE *resolve_help(int dcc, char *file)
1186 {
1187 
1188   char s[1024];
1189   FILE *f;
1190   struct help_ref *current;
1191   struct help_list_t *item;
1192 
1193   /* Somewhere here goes the eventual substitution */
1194   if (!(dcc & HELP_TEXT)) {
1195     for (current = help_list; current; current = current->next)
1196       for (item = current->first; item; item = item->next)
1197         if (!strcmp(item->name, file)) {
1198           if (!item->type && !dcc) {
1199             egg_snprintf(s, sizeof s, "%smsg/%s", helpdir, current->name);
1200             if ((f = fopen(s, "r")))
1201               return f;
1202           } else if (dcc && item->type) {
1203             if (item->type == 1)
1204               egg_snprintf(s, sizeof s, "%s%s", helpdir, current->name);
1205             else
1206               egg_snprintf(s, sizeof s, "%sset/%s", helpdir, current->name);
1207             if ((f = fopen(s, "r")))
1208               return f;
1209           }
1210         }
1211     /* No match was found, so we better return NULL */
1212     return NULL;
1213   }
1214   /* Since we're not dealing with help files, we should just prepend the filename with textdir */
1215   simple_sprintf(s, "%s%s", textdir, file);
1216   if (is_file(s))
1217     return fopen(s, "r");
1218   else
1219     return NULL;
1220 }
1221 
showhelp(char * who,char * file,struct flag_record * flags,int fl)1222 void showhelp(char *who, char *file, struct flag_record *flags, int fl)
1223 {
1224   int lines = 0;
1225   char s[HELP_BUF_LEN + 1];
1226   FILE *f = resolve_help(fl, file);
1227 
1228   if (f) {
1229     help_subst(NULL, NULL, 0, HELP_IRC, NULL);  /* Clear flags */
1230     /* don't check for feof after fgets, skips last line if it has no \n (ie on windows) */
1231     while (!feof(f) && fgets(s, HELP_BUF_LEN, f) != NULL) {
1232       if (s[strlen(s) - 1] == '\n')
1233         s[strlen(s) - 1] = 0;
1234       if (!s[0])
1235         strcpy(s, " ");
1236       help_subst(s, who, flags, 0, file);
1237       if ((s[0]) && (strlen(s) > 1)) {
1238         dprintf(DP_HELP, "NOTICE %s :%s\n", who, s);
1239         lines++;
1240       }
1241     }
1242     /* fgets == NULL means error or empty file, so check for error */
1243     if (ferror(f)) {
1244       putlog(LOG_MISC, "*", "Error reading help file");
1245     }
1246     fclose(f);
1247   }
1248   if (!lines && !(fl & HELP_TEXT))
1249     dprintf(DP_HELP, "NOTICE %s :%s\n", who, IRC_NOHELP2);
1250 }
1251 
display_tellhelp(int idx,char * file,FILE * f,struct flag_record * flags)1252 static int display_tellhelp(int idx, char *file, FILE *f,
1253                             struct flag_record *flags)
1254 {
1255   char s[HELP_BUF_LEN + 1];
1256   int lines = 0;
1257 
1258   if (f) {
1259     help_subst(NULL, NULL, 0,
1260                (dcc[idx].status & STAT_TELNET) ? 0 : HELP_IRC, NULL);
1261     /* don't check for feof after fgets, skips last line if it has no \n (ie on windows) */
1262     while (!feof(f) && fgets(s, HELP_BUF_LEN, f) != NULL) {
1263       if (s[strlen(s) - 1] == '\n')
1264         s[strlen(s) - 1] = 0;
1265       if (!s[0])
1266         strcpy(s, " ");
1267       help_subst(s, dcc[idx].nick, flags, 1, file);
1268       if (s[0]) {
1269         dprintf(idx, "%s\n", s);
1270         lines++;
1271       }
1272     }
1273     /* fgets == NULL means error or empty file, so check for error */
1274     if (ferror(f)) {
1275       putlog(LOG_MISC, "*", "Error displaying help");
1276     }
1277     fclose(f);
1278   }
1279   return lines;
1280 }
1281 
tellhelp(int idx,char * file,struct flag_record * flags,int fl)1282 void tellhelp(int idx, char *file, struct flag_record *flags, int fl)
1283 {
1284   int lines = 0;
1285   FILE *f = resolve_help(HELP_DCC | fl, file);
1286 
1287   if (f)
1288     lines = display_tellhelp(idx, file, f, flags);
1289   if (!lines && !(fl & HELP_TEXT))
1290     dprintf(idx, "%s\n", IRC_NOHELP2);
1291 }
1292 
1293 /* Same as tellallhelp, just using wild_match instead of strcmp
1294  */
tellwildhelp(int idx,char * match,struct flag_record * flags)1295 void tellwildhelp(int idx, char *match, struct flag_record *flags)
1296 {
1297   struct help_ref *current;
1298   struct help_list_t *item;
1299   FILE *f;
1300   char s[1024];
1301 
1302   s[0] = '\0';
1303   for (current = help_list; current; current = current->next)
1304     for (item = current->first; item; item = item->next)
1305       if (wild_match(match, item->name) && item->type) {
1306         if (item->type == 1)
1307           egg_snprintf(s, sizeof s, "%s%s", helpdir, current->name);
1308         else
1309           egg_snprintf(s, sizeof s, "%sset/%s", helpdir, current->name);
1310         if ((f = fopen(s, "r")))
1311           display_tellhelp(idx, item->name, f, flags);
1312       }
1313   if (!s[0])
1314     dprintf(idx, "%s\n", IRC_NOHELP2);
1315 }
1316 
1317 /* Same as tellwildhelp, just using strcmp instead of wild_match
1318  */
tellallhelp(int idx,char * match,struct flag_record * flags)1319 void tellallhelp(int idx, char *match, struct flag_record *flags)
1320 {
1321   struct help_ref *current;
1322   struct help_list_t *item;
1323   FILE *f;
1324   char s[1024];
1325 
1326   s[0] = '\0';
1327   for (current = help_list; current; current = current->next)
1328     for (item = current->first; item; item = item->next)
1329       if (!strcmp(match, item->name) && item->type) {
1330 
1331         if (item->type == 1)
1332           egg_snprintf(s, sizeof s, "%s%s", helpdir, current->name);
1333         else
1334           egg_snprintf(s, sizeof s, "%sset/%s", helpdir, current->name);
1335         if ((f = fopen(s, "r")))
1336           display_tellhelp(idx, item->name, f, flags);
1337       }
1338   if (!s[0])
1339     dprintf(idx, "%s\n", IRC_NOHELP2);
1340 }
1341 
1342 /* Substitute vars in a lang text to dcc chatter
1343  */
sub_lang(int idx,char * text)1344 void sub_lang(int idx, char *text)
1345 {
1346   char s[1024];
1347   struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };
1348 
1349   get_user_flagrec(dcc[idx].user, &fr, dcc[idx].u.chat->con_chan);
1350   help_subst(NULL, NULL, 0,
1351              (dcc[idx].status & STAT_TELNET) ? 0 : HELP_IRC, NULL);
1352   strlcpy(s, text, sizeof s);
1353   if (s[strlen(s) - 1] == '\n')
1354     s[strlen(s) - 1] = 0;
1355   if (!s[0])
1356     strcpy(s, " ");
1357   help_subst(s, dcc[idx].nick, &fr, 1, botnetnick);
1358   if (s[0])
1359     dprintf(idx, "%s\n", s);
1360 }
1361 
1362 /* This will return a pointer to the first character after the @ in the
1363  * string given it.  Possibly it's time to think about a regexp library
1364  * for eggdrop...
1365  */
extracthostname(char * hostmask)1366 char *extracthostname(char *hostmask)
1367 {
1368   char *p = strrchr(hostmask, '@');
1369 
1370   return p ? p + 1 : "";
1371 }
1372 
1373 /* Show motd to dcc chatter
1374  */
show_motd(int idx)1375 void show_motd(int idx)
1376 {
1377   FILE *vv;
1378   char s[1024];
1379   struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };
1380 
1381   if (!is_file(motdfile))
1382     return;
1383 
1384   vv = fopen(motdfile, "r");
1385   if (!vv)
1386     return;
1387 
1388   get_user_flagrec(dcc[idx].user, &fr, dcc[idx].u.chat->con_chan);
1389   dprintf(idx, "\n");
1390   /* reset the help_subst variables to their defaults */
1391   help_subst(NULL, NULL, 0,
1392              (dcc[idx].status & STAT_TELNET) ? 0 : HELP_IRC, NULL);
1393   /* don't check for feof after fgets, skips last line if it has no \n (ie on windows) */
1394   while (!feof(vv) && fgets(s, sizeof s, vv) != NULL) {
1395     if (s[strlen(s) - 1] == '\n')
1396       s[strlen(s) - 1] = 0;
1397     if (!s[0])
1398       strcpy(s, " ");
1399     help_subst(s, dcc[idx].nick, &fr, 1, botnetnick);
1400     if (s[0])
1401       dprintf(idx, "%s\n", s);
1402   }
1403   /* fgets == NULL means error or empty file, so check for error */
1404   if (ferror(vv)) {
1405     putlog(LOG_MISC, "*", "Error reading MOTD for DCC");
1406   }
1407   fclose(vv);
1408   dprintf(idx, "\n");
1409 }
1410 
1411 /* Show banner to telnet user
1412  */
show_banner(int idx)1413 void show_banner(int idx)
1414 {
1415   FILE *vv;
1416   char s[1024];
1417   struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };
1418 
1419   if (!is_file(bannerfile))
1420     return;
1421 
1422   vv = fopen(bannerfile, "r");
1423   if (!vv)
1424     return;
1425 
1426   get_user_flagrec(dcc[idx].user, &fr, dcc[idx].u.chat->con_chan);
1427   /* reset the help_subst variables to their defaults */
1428   help_subst(NULL, NULL, 0, 0, NULL);
1429   /* don't check for feof after fgets, skips last line if it has no \n (ie on windows) */
1430   while (!feof(vv) && fgets(s, sizeof s, vv) != NULL) {
1431     if (s[strlen(s) - 1] == '\n')
1432       s[strlen(s) - 1] = 0;
1433     if (!s[0])
1434       strcpy(s, " ");
1435     help_subst(s, dcc[idx].nick, &fr, 0, botnetnick);
1436     if (s[0])
1437       dprintf(idx, "%s\n", s);
1438   }
1439   /* fgets == NULL means error or empty file, so check for error */
1440   if (ferror(vv)) {
1441       putlog(LOG_MISC, "*", "Error reading banner");
1442   }
1443   fclose(vv);
1444 }
1445 
make_rand_str_from_chars(char * s,const int len,char * chars)1446 void make_rand_str_from_chars(char *s, const int len, char *chars)
1447 {
1448   int i;
1449 
1450   for (i = 0; i < len; i++)
1451     s[i] = chars[randint(strlen(chars))];
1452   s[len] = 0;
1453 }
1454 
1455 /* Create a string with random lower case letters and digits
1456  */
make_rand_str(char * s,const int len)1457 void make_rand_str(char *s, const int len)
1458 {
1459   make_rand_str_from_chars(s, len, CHARSET_LOWER_ALPHA_NUM);
1460 }
1461 
1462 /* Convert an octal string into a decimal integer value.  If the string
1463  * is empty or contains non-octal characters, -1 is returned.
1464  */
oatoi(const char * octal)1465 int oatoi(const char *octal)
1466 {
1467   int i;
1468 
1469   if (!*octal)
1470     return -1;
1471   for (i = 0; ((*octal >= '0') && (*octal <= '7')); octal++)
1472     i = (i * 8) + (*octal - '0');
1473   if (*octal)
1474     return -1;
1475   return i;
1476 }
1477 
1478 /* Return an allocated buffer which contains a copy of the string
1479  * 'str', with all 'div' characters escaped by 'mask'. 'mask'
1480  * characters are escaped too.
1481  *
1482  * Remember to free the returned memory block.
1483  */
str_escape(const char * str,const char div,const char mask)1484 char *str_escape(const char *str, const char div, const char mask)
1485 {
1486   const int len = strlen(str);
1487   int buflen = (2 * len), blen = 0;
1488   char *buf = nmalloc(buflen + 1), *b = buf;
1489   const char *s;
1490 
1491   if (!buf)
1492     return NULL;
1493   for (s = str; *s; s++) {
1494     /* Resize buffer. */
1495     if ((buflen - blen) <= 3) {
1496       buflen = (buflen * 2);
1497       buf = nrealloc(buf, buflen + 1);
1498       if (!buf)
1499         return NULL;
1500       b = buf + blen;
1501     }
1502 
1503     if (*s == div || *s == mask) {
1504       sprintf(b, "%c%02x", mask, *s);
1505       b += 3;
1506       blen += 3;
1507     } else {
1508       *(b++) = *s;
1509       blen++;
1510     }
1511   }
1512   *b = 0;
1513   return buf;
1514 }
1515 
1516 /* Search for a certain character 'div' in the string 'str', while
1517  * ignoring escaped characters prefixed with 'mask'.
1518  *
1519  * The string
1520  *
1521  *   "\\3a\\5c i am funny \\3a):further text\\5c):oink"
1522  *
1523  * as str, '\\' as mask and ':' as div would change the str buffer
1524  * to
1525  *
1526  *   ":\\ i am funny :)"
1527  *
1528  * and return a pointer to "further text\\5c):oink".
1529  *
1530  * NOTE: If you look carefully, you'll notice that strchr_unescape()
1531  *       behaves differently than strchr().
1532  */
strchr_unescape(char * str,const char div,const char esc_char)1533 char *strchr_unescape(char *str, const char div, const char esc_char)
1534 {
1535   char buf[3];
1536   char *s, *p;
1537 
1538   buf[2] = 0;
1539   for (s = p = str; *s; s++, p++) {
1540     if (*s == esc_char) {       /* Found escape character.              */
1541       /* Convert code to character. */
1542       buf[0] = s[1], buf[1] = s[2];
1543       *p = (unsigned char) strtol(buf, NULL, 16);
1544       s += 2;
1545     } else if (*s == div) {
1546       *p = *s = 0;
1547       return (s + 1);           /* Found searched for character.        */
1548     } else
1549       *p = *s;
1550   }
1551   *p = 0;
1552   return NULL;
1553 }
1554 
1555 /* Is every character in a string a digit? */
str_isdigit(const char * str)1556 int str_isdigit(const char *str)
1557 {
1558   if (!*str)
1559     return 0;
1560 
1561   for (; *str; ++str) {
1562     if (!egg_isdigit(*str))
1563       return 0;
1564   }
1565   return 1;
1566 }
1567 
1568 /* As strchr_unescape(), but converts the complete string, without
1569  * searching for a specific delimiter character.
1570  */
str_unescape(char * str,const char esc_char)1571 void str_unescape(char *str, const char esc_char)
1572 {
1573   (void) strchr_unescape(str, 0, esc_char);
1574 }
1575 
1576 /* Kills the bot. s1 is the reason shown to other bots,
1577  * s2 the reason shown on the partyline. (Sup 25Jul2001)
1578  */
kill_bot(char * s1,char * s2)1579 void kill_bot(char *s1, char *s2)
1580 {
1581   check_tcl_die(s2);
1582   call_hook(HOOK_DIE);
1583   chatout("*** %s\n", s1);
1584   botnet_send_chat(-1, botnetnick, s1);
1585   botnet_send_bye();
1586   write_userfile(-1);
1587   fatal(s2, 2);
1588 }
1589 
1590 /* Compares two strings with constant-time algorithm to avoid timing attack and
1591  * returns 0, if strings match, similar to strcmp().
1592  */
1593 /* https://github.com/jedisct1/libsodium/blob/451bafc0d3d95d18f916dd7051687d343597228c/src/libsodium/crypto_verify/sodium/verify.c */
1594 /*
1595  * ISC License
1596  *
1597  * Copyright (c) 2013-2020
1598  * Frank Denis <j at pureftpd dot org>
1599  *
1600  * Permission to use, copy, modify, and/or distribute this software for any
1601  * purpose with or without fee is hereby granted, provided that the above
1602  * copyright notice and this permission notice appear in all copies.
1603  *
1604  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1605  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1606  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1607  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1608  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1609  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1610  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1611  */
crypto_verify(const char * x_,const char * y_)1612 int crypto_verify(const char *x_, const char *y_)
1613 {
1614   const volatile unsigned char *volatile x =
1615     (const volatile unsigned char *volatile) x_;
1616   const volatile unsigned char *volatile y =
1617     (const volatile unsigned char *volatile) y_;
1618   volatile uint_fast16_t d = 0U;
1619   int n, i;
1620 
1621   /* Could leak string length */
1622   n = strlen(x_);
1623   if (n != strlen(y_))
1624     return 1;
1625 
1626   for (i = 0; i < n; i++) {
1627     d |= x[i] ^ y[i];
1628   }
1629   return (1 & ((d - 1) >> 8)) - 1;
1630 }
1631