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