161e9d2bd9e4e2b8bbe9cded2fa7d20fdc461792
[srvx.git] / src / tools.c
1 /* tools.c - miscellaneous utility functions
2  * Copyright 2000-2004 srvx Development Team
3  *
4  * This file is part of srvx.
5  *
6  * srvx is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with srvx; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
19  */
20
21 #include "helpfile.h"
22 #include "log.h"
23 #include "nickserv.h"
24 #include "recdb.h"
25
26 #ifdef HAVE_NETDB_H
27 #include <netdb.h>
28 #endif
29 #ifdef HAVE_SYS_SOCKET_H
30 #include <sys/socket.h>
31 #endif
32 #ifdef HAVE_ARPA_INET_H
33 #include <arpa/inet.h>
34 #endif
35
36 #define NUMNICKLOG 6
37 #define NUMNICKBASE (1 << NUMNICKLOG)
38 #define NUMNICKMASK (NUMNICKBASE - 1)
39
40 /* Yes, P10's encoding here is almost-but-not-quite MIME Base64.  Yay
41  * for gratuitous incompatibilities. */
42 static const char convert2y[256] = {
43   'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
44   'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
45   'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
46   'w','x','y','z','0','1','2','3','4','5','6','7','8','9','[',']'
47 };
48
49 static const unsigned char convert2n[256] = {
50    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
51    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
52    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
53   52,53,54,55,56,57,58,59,60,61, 0, 0, 0, 0, 0, 0, 
54    0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
55   15,16,17,18,19,20,21,22,23,24,25,62, 0,63, 0, 0,
56    0,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
57   41,42,43,44,45,46,47,48,49,50,51, 0, 0, 0, 0, 0
58 };
59
60 unsigned long int
61 base64toint(const char* s, int count)
62 {
63     unsigned int i = 0;
64     while (*s && count) {
65         i = (i << NUMNICKLOG) + convert2n[(unsigned char)*s++];
66         count--;
67     }
68     return i;
69 }
70
71 const char* inttobase64(char* buf, unsigned int v, unsigned int count)
72 {
73   buf[count] = '\0';
74   while (count > 0) {
75       buf[--count] = convert2y[(unsigned char)(v & NUMNICKMASK)];
76       v >>= NUMNICKLOG;
77   }
78   return buf;
79 }
80
81 static char irc_tolower[256];
82 #undef tolower
83 #define tolower(X) irc_tolower[(unsigned char)(X)]
84
85 int
86 irccasecmp(const char *stra, const char *strb) {
87     while (*stra && (tolower(*stra) == tolower(*strb)))
88         stra++, strb++;
89     return tolower(*stra) - tolower(*strb);
90 }
91
92 int
93 ircncasecmp(const char *stra, const char *strb, unsigned int len) {
94     len--;
95     while (*stra && (tolower(*stra) == tolower(*strb)) && len)
96         stra++, strb++, len--;
97     return tolower(*stra) - tolower(*strb);
98 }
99
100 const char *
101 irccasestr(const char *haystack, const char *needle) {
102     unsigned int hay_len = strlen(haystack), needle_len = strlen(needle), pos;
103     if (hay_len < needle_len)
104         return NULL;
105     for (pos=0; pos<hay_len+1-needle_len; ++pos) {
106         if ((tolower(haystack[pos]) == tolower(*needle))
107             && !ircncasecmp(haystack+pos, needle, needle_len))
108             return haystack+pos;
109     }
110     return NULL;
111 }
112
113 int
114 split_line(char *line, int irc_colon, int argv_size, char *argv[])
115 {
116     int argc = 0;
117     int n;
118     while (*line && (argc < argv_size)) {
119         while (*line == ' ')
120             *line++ = 0;
121         if (*line == ':' && irc_colon && argc > 0) {
122             /* the rest is a single parameter */
123             argv[argc++] = line + 1;
124             break;
125         }
126         if (!*line)
127             break;
128         argv[argc++] = line;
129         if (argc >= argv_size)
130             break;
131         while (*line != ' ' && *line)
132             line++;
133     }
134 #ifdef NDEBUG
135     n = 0;
136 #else
137     for (n=argc; n<argv_size; n++)
138         argv[n] = (char*)0xFEEDBEEF;
139 #endif
140     return argc;
141 }
142
143 /* This is ircu's mmatch() function, from match.c. */
144 int mmatch(const char *old_mask, const char *new_mask)
145 {
146   register const char *m = old_mask;
147   register const char *n = new_mask;
148   const char *ma = m;
149   const char *na = n;
150   int wild = 0;
151   int mq = 0, nq = 0;
152
153   while (1)
154   {
155     if (*m == '*')
156     {
157       while (*m == '*')
158         m++;
159       wild = 1;
160       ma = m;
161       na = n;
162     }
163
164     if (!*m)
165     {
166       if (!*n)
167         return 0;
168       for (m--; (m > old_mask) && (*m == '?'); m--)
169         ;
170       if ((*m == '*') && (m > old_mask) && (m[-1] != '\\'))
171         return 0;
172       if (!wild)
173         return 1;
174       m = ma;
175
176       /* Added to `mmatch' : Because '\?' and '\*' now is one character: */
177       if ((*na == '\\') && ((na[1] == '*') || (na[1] == '?')))
178         ++na;
179
180       n = ++na;
181     }
182     else if (!*n)
183     {
184       while (*m == '*')
185         m++;
186       return (*m != 0);
187     }
188     if ((*m == '\\') && ((m[1] == '*') || (m[1] == '?')))
189     {
190       m++;
191       mq = 1;
192     }
193     else
194       mq = 0;
195
196     /* Added to `mmatch' : Because '\?' and '\*' now is one character: */
197     if ((*n == '\\') && ((n[1] == '*') || (n[1] == '?')))
198     {
199       n++;
200       nq = 1;
201     }
202     else
203       nq = 0;
204
205 /*
206  * This `if' has been changed compared to match() to do the following:
207  * Match when:
208  *   old (m)         new (n)         boolean expression
209  *    *               any             (*m == '*' && !mq) ||
210  *    ?               any except '*'  (*m == '?' && !mq && (*n != '*' || nq)) ||
211  * any except * or ?  same as m       (!((*m == '*' || *m == '?') && !mq) &&
212  *                                      toLower(*m) == toLower(*n) &&
213  *                                        !((mq && !nq) || (!mq && nq)))
214  *
215  * Here `any' also includes \* and \? !
216  *
217  * After reworking the boolean expressions, we get:
218  * (Optimized to use boolean shortcircuits, with most frequently occuring
219  *  cases upfront (which took 2 hours!)).
220  */
221     if ((*m == '*' && !mq) ||
222         ((!mq || nq) && tolower(*m) == tolower(*n)) ||
223         (*m == '?' && !mq && (*n != '*' || nq)))
224     {
225       if (*m)
226         m++;
227       if (*n)
228         n++;
229     }
230     else
231     {
232       if (!wild)
233         return 1;
234       m = ma;
235
236       /* Added to `mmatch' : Because '\?' and '\*' now is one character: */
237       if ((*na == '\\') && ((na[1] == '*') || (na[1] == '?')))
238         ++na;
239
240       n = ++na;
241     }
242   }
243 }
244
245 int
246 match_ircglob(const char *text, const char *glob)
247 {
248     unsigned int star_p, q_cnt;
249     while (1) {
250         switch (*glob) {
251         case 0:
252             return !*text;
253         case '\\':
254             glob++;
255             /* intentionally not tolower(...) so people can force
256              * capitalization, or we can overload \ in the future */
257             if (*text++ != *glob++)
258                 return 0;
259             break;
260         case '*':
261         case '?':
262             star_p = q_cnt = 0;
263             do {
264                 if (*glob == '*')
265                     star_p = 1;
266                 else if (*glob == '?')
267                     q_cnt++;
268                 else
269                     break;
270                 glob++;
271             } while (1);
272             while (q_cnt) {
273                 if (!*text++)
274                     return 0;
275                 q_cnt--;
276             }
277             if (star_p) {
278                 /* if this is the last glob character, it will match any text */
279                 if (!*glob)
280                     return 1;
281                 /* Thanks to the loop above, we know that the next
282                  * character is a normal character.  So just look for
283                  * the right character.
284                  */
285                 for (; *text; text++) {
286                     if ((tolower(*text) == tolower(*glob))
287                         && match_ircglob(text+1, glob+1)) {
288                         return 1;
289                     }
290                 }
291                 return 0;
292             }
293             /* if !star_p, fall through to normal character case,
294              * first checking to see if ?s carried us to the end */
295             if (!*glob && !*text)
296                 return 1;
297         default:
298             if (!*text)
299                 return 0;
300             while (*text && *glob && *glob != '*' && *glob != '?' && *glob != '\\') {
301                 if (tolower(*text++) != tolower(*glob++))
302                     return 0;
303             }
304         }
305     }
306 }
307
308 extern const char *hidden_host_suffix;
309
310 int
311 user_matches_glob(struct userNode *user, const char *orig_glob, int include_nick)
312 {
313     char *glob, *marker;
314
315     /* Make a writable copy of the glob */
316     glob = alloca(strlen(orig_glob)+1);
317     strcpy(glob, orig_glob);
318     /* Check the nick, if it's present */
319     if (include_nick) {
320         if (!(marker = strchr(glob, '!'))) {
321             log_module(MAIN_LOG, LOG_ERROR, "user_matches_glob(\"%s\", \"%s\", %d) called, and glob doesn't include a '!'", user->nick, orig_glob, include_nick);
322             return 0;
323         }
324         *marker = 0;
325         if (!match_ircglob(user->nick, glob)) return 0;
326         glob = marker + 1;
327     }
328     /* Check the ident */
329     if (!(marker = strchr(glob, '@'))) {
330         log_module(MAIN_LOG, LOG_ERROR, "user_matches_glob(\"%s\", \"%s\", %d) called, and glob doesn't include an '@'", user->nick, orig_glob, include_nick);
331         return 0;
332     }
333     *marker = 0;
334     if (!match_ircglob(user->ident, glob))
335         return 0;
336     glob = marker + 1;
337     /* Now check the host part */
338     if (isdigit(*glob) && !glob[strspn(glob, "0123456789./*?")]) {
339         /* Looks like an IP-based mask */
340         return match_ircglob(inet_ntoa(user->ip), glob);
341     } else {
342         /* The host part of the mask isn't IP-based */
343         if (IsFakeHost(user) && match_ircglob(user->fakehost, glob))
344                 return 1;
345         if (hidden_host_suffix && user->handle_info) {
346             char hidden_host[HOSTLEN+1];
347             snprintf(hidden_host, sizeof(hidden_host), "%s.%s", user->handle_info->handle, hidden_host_suffix);
348             if (match_ircglob(hidden_host, glob))
349                 return 1;
350         }
351         return match_ircglob(user->hostname, glob);
352     }
353 }
354
355 int
356 is_ircmask(const char *text)
357 {
358     while (*text && (isalnum((char)*text) || strchr("-_[]|\\`^{}?*", *text)))
359         text++;
360     if (*text++ != '!')
361         return 0;
362     while (*text && *text != '@' && !isspace((char)*text))
363         text++;
364     if (*text++ != '@')
365         return 0;
366     while (*text && !isspace((char)*text))
367         text++;
368     return !*text;
369 }
370
371 int
372 is_gline(const char *text)
373 {
374     if (*text == '@')
375         return 0;
376     text += strcspn(text, "@!% \t\r\n");
377     if (*text++ != '@')
378         return 0;
379     if (!*text)
380         return 0;
381     while (*text && (isalnum((char)*text) || strchr(".-?*", *text)))
382         text++;
383     return !*text;
384 }
385
386 int
387 split_ircmask(char *text, char **nick, char **ident, char **host)
388 {
389     char *start;
390
391     start = text;
392     while (isalnum((char)*text) || strchr("=[]\\`^{}?*", *text))
393         text++;
394     if (*text != '!' || ((text - start) > NICKLEN))
395         return 0;
396     *text = 0;
397     if (nick)
398         *nick = start;
399
400     start = ++text;
401     while (*text && *text != '@' && !isspace((char)*text))
402         text++;
403     if (*text != '@' || ((text - start) > USERLEN))
404         return 0;
405     *text = 0;
406     if (ident)
407         *ident = start;
408     
409     start = ++text;
410     while (*text && (isalnum((char)*text) || strchr(".-?*", *text)))
411         text++;
412     if (host)
413         *host = start;
414     return !*text && ((text - start) <= HOSTLEN) && nick && ident && host;
415 }
416
417 char *
418 sanitize_ircmask(char *input)
419 {
420     unsigned int length, flag;
421     char *mask, *start, *output;
422
423     /* Sanitize everything in place; input *must* be a valid
424        hostmask. */
425     output = input;
426     flag = 0;
427
428     /* The nick is truncated at the end. */
429     length = 0;
430     mask = input;
431     while(*input++ != '!')
432     {
433         length++;
434     }
435     if(length > NICKLEN)
436     {
437         mask += NICKLEN;
438         *mask++ = '!';
439
440         /* This flag is used to indicate following parts should
441            be shifted. */
442         flag = 1;
443     }
444     else
445     {
446         mask = input;
447     }
448
449     /* The ident and host must be truncated at the beginning and
450        replaced with a '*' to be compatible with ircu. */
451     length = 0;
452     start = input;
453     while(*input++ != '@')
454     {
455         length++;
456     }
457     if(length > USERLEN || flag)
458     {
459         if(length > USERLEN)
460         {
461             start = input - USERLEN;
462             *mask++ = '*';
463         }
464         while(*start != '@')
465         {
466             *mask++ = *start++;
467         }
468         *mask++ = '@';
469
470         flag = 1;
471     }
472     else
473     {
474         mask = input;
475     }
476
477     length = 0;
478     start = input;
479     while(*input++)
480     {
481         length++;
482     }
483     if(length > HOSTLEN || flag)
484     {
485         if(length > HOSTLEN)
486         {
487             start = input - HOSTLEN;
488             *mask++ = '*';
489         }
490         while(*start)
491         {
492             *mask++ = *start++;
493         }
494         *mask = '\0';
495     }
496
497     return output;
498 }
499
500 static long
501 TypeLength(char type)
502 {
503     switch (type) {
504     case 'y': return 365*24*60*60;
505     case 'M': return 31*24*60*60;
506     case 'w': return 7*24*60*60;
507     case 'd': return 24*60*60;
508     case 'h': return 60*60;
509     case 'm': return 60;
510     case 's': return 1;
511     default: return 0;
512     }
513 }
514
515 unsigned long
516 ParseInterval(const char *interval)
517 {
518     unsigned long seconds = 0;
519     int partial = 0;
520     char c;
521
522     /* process the string, resetting the count if we find a unit character */
523     while ((c = *interval++)) {
524         if (isdigit((int)c)) {
525             partial = partial*10 + c - '0';
526         } else {
527             seconds += TypeLength(c) * partial;
528             partial = 0;
529         }
530     }
531     /* assume the last chunk is seconds (the normal case) */
532     return seconds + partial;
533 }
534
535 static long
536 GetSizeMultiplier(char type)
537 {
538     switch (type) {
539     case 'G': case 'g': return 1024*1024*1024;
540     case 'M': case 'm': return 1024*1024;
541     case 'K': case 'k': return 1024;
542     case 'B': case 'b': return 1;
543     default: return 0;
544     }
545 }
546
547 unsigned long
548 ParseVolume(const char *volume)
549 {
550     unsigned long accum = 0, partial = 0;
551     char c;
552     while ((c = *volume++)) {
553         if (isdigit((int)c)) {
554             partial = partial*10 + c - '0';
555         } else {
556             accum += GetSizeMultiplier(c) * partial;
557             partial = 0;
558         }
559     }
560     return accum + partial;
561 }
562
563 int
564 parse_ipmask(const char *str, struct in_addr *addr, unsigned long *mask)
565 {
566     int accum, pos;
567     unsigned long t_a, t_m;
568
569     t_a = t_m = pos = 0;
570     if (addr)
571         addr->s_addr = htonl(t_a);
572     if (mask)
573         *mask = t_m;
574     while (*str) {
575         if (!isdigit(*str))
576             return 0;
577         accum = 0;
578         do {
579             accum = (accum * 10) + *str++ - '0';
580         } while (isdigit(*str));
581         if (accum > 255)
582             return 0;
583         t_a = (t_a << 8) | accum;
584         t_m = (t_m << 8) | 255;
585         pos += 8;
586         if (*str == '.') {
587             str++;
588             while (*str == '*') {
589                 str++;
590                 if (*str == '.') {
591                     t_a <<= 8;
592                     t_m <<= 8;
593                     pos += 8;
594                     str++;
595                 } else if (*str == 0) {
596                     t_a <<= 32 - pos;
597                     t_m <<= 32 - pos;
598                     pos = 32;
599                     goto out;
600                 } else
601                     return 0;
602             }
603         } else if (*str == '/') {
604             int start = pos;
605             accum = 0;
606             do {
607                 accum = (accum * 10) + *str++ - '0';
608             } while (isdigit(*str));
609             while (pos < start+accum && pos < 32) {
610                 t_a = (t_a << 1) | 0;
611                 t_m = (t_m << 1) | 1;
612                 pos++;
613             }
614             if (pos != start+accum)
615                 return 0;
616         } else if (*str == 0)
617             break;
618         else
619             return 0;
620     }
621 out:
622     if (pos != 32)
623         return 0;
624     if (addr)
625         addr->s_addr = htonl(t_a);
626     if (mask)
627         *mask = t_m;
628     return 1;
629 }
630
631 char *
632 unsplit_string(char *set[], unsigned int max, char *dest)
633 {
634     static char unsplit_buffer[MAXLEN*2];
635     unsigned int ii, jj, pos;
636
637     if (!dest)
638         dest = unsplit_buffer;
639     for (ii=pos=0; ii<max; ii++) {
640         for (jj=0; set[ii][jj]; jj++)
641             dest[pos++] = set[ii][jj];
642         dest[pos++] = ' ';
643     }
644     dest[--pos] = 0;
645     return dest;
646 }
647
648 char *
649 intervalString(char *output, time_t interval, struct handle_info *hi)
650 {
651     static const struct {
652         const char *msg_single;
653         const char *msg_plural;
654         long length;
655     } unit[] = {
656         { "MSG_YEAR",   "MSG_YEARS", 365 * 24 * 60 * 60 },
657         { "MSG_WEEK",   "MSG_WEEKS",   7 * 24 * 60 * 60 },
658         { "MSG_DAY",    "MSG_DAYS",        24 * 60 * 60 },
659         { "MSG_HOUR",   "MSG_HOURS",            60 * 60 },
660         { "MSG_MINUTE", "MSG_MINUTES",               60 },
661         { "MSG_SECOND", "MSG_SECONDS",                1 }
662     };
663     struct language *lang;
664     const char *msg;
665     unsigned int type, words, pos, count;
666
667     lang = hi ? hi->language : lang_C;
668     if(!interval)
669     {
670         msg = language_find_message(lang, "MSG_0_SECONDS");
671         return strcpy(output, msg);
672     }
673
674     for (type = 0, words = pos = 0;
675          interval && (words < 2) && (type < ArrayLength(unit));
676          type++) {
677         if (interval < unit[type].length)
678             continue;
679         count = interval / unit[type].length;
680         interval = interval % unit[type].length;
681
682         if (words++ == 1) {
683             msg = language_find_message(lang, "MSG_AND");
684             pos += sprintf(output + pos, " %s ", msg);
685         }
686         if (count == 1)
687             msg = language_find_message(lang, unit[type].msg_single);
688         else
689             msg = language_find_message(lang, unit[type].msg_plural);
690         pos += sprintf(output + pos, "%d %s", count, msg);
691     }
692
693     output[pos] = 0;
694     return output;
695 }
696
697 int
698 getipbyname(const char *name, unsigned long *ip)
699 {
700     struct hostent *he = gethostbyname(name);
701     if (!he)
702         return 0;
703     if (he->h_addrtype != AF_INET)
704         return 0;
705     memcpy(ip, he->h_addr_list[0], sizeof(*ip));
706     return 1;
707 }
708
709 DEFINE_LIST(string_buffer, char)
710
711 void
712 string_buffer_append_substring(struct string_buffer *buf, const char *tail, unsigned int len)
713 {
714     while (buf->used + len >= buf->size) {
715         if (!buf->size)
716             buf->size = 16;
717         else
718             buf->size <<= 1;
719         buf->list = realloc(buf->list, buf->size*sizeof(buf->list[0]));
720     }
721     memcpy(buf->list + buf->used, tail, len+1);
722     buf->used += len;
723 }
724
725 void
726 string_buffer_append_string(struct string_buffer *buf, const char *tail)
727 {
728     string_buffer_append_substring(buf, tail, strlen(tail));
729 }
730
731 void
732 string_buffer_append_vprintf(struct string_buffer *buf, const char *fmt, va_list args)
733 {
734     va_list working;
735     unsigned int len;
736     int ret;
737
738     VA_COPY(working, args);
739     len = strlen(fmt);
740     if (!buf->list || ((buf->used + buf->size) < len)) {
741         buf->size = buf->used + len;
742         buf->list = realloc(buf->list, buf->size);
743     }
744     ret = vsnprintf(buf->list + buf->used, buf->size - buf->used, fmt, working);
745     if (ret <= 0) {
746         /* pre-C99 behavior; double buffer size until it is big enough */
747         va_end(working);
748         VA_COPY(working, args);
749         while ((ret = vsnprintf(buf->list + buf->used, buf->size, fmt, working)) == -1) {
750             buf->size += len;
751             buf->list = realloc(buf->list, buf->size);
752             va_end(working);
753             VA_COPY(working, args);
754         }
755         buf->used += ret;
756     } else if (buf->used + ret < buf->size) {
757         /* no need to increase allocation size */
758         buf->used += ret;
759     } else {
760         /* now we know exactly how much space we need */
761         if (buf->size <= buf->used + ret) {
762             buf->size = buf->used + ret + 1;
763             buf->list = realloc(buf->list, buf->size);
764         }
765         va_end(working);
766         VA_COPY(working, args);
767         buf->used += vsnprintf(buf->list + buf->used, buf->size, fmt, working);
768     }
769     va_end(working);
770     va_end(args);
771 }
772
773 void string_buffer_append_printf(struct string_buffer *buf, const char *fmt, ...)
774 {
775     va_list args;
776     va_start(args, fmt);
777     string_buffer_append_vprintf(buf, fmt, args);
778 }
779
780 void
781 string_buffer_replace(struct string_buffer *buf, unsigned int from, unsigned int len, const char *repl)
782 {
783     unsigned int repl_len = strlen(repl);
784     if (from > buf->used)
785         return;
786     if (len + from > buf->used)
787         len = buf->used - from;
788     buf->used = buf->used + repl_len - len;
789     if (buf->size <= buf->used) {
790         while (buf->used >= buf->size)
791             buf->size <<= 1;
792         buf->list = realloc(buf->list, buf->size*sizeof(buf->list[0]));
793     }
794     memmove(buf->list+from+repl_len, buf->list+from+len, strlen(buf->list+from+len));
795     strcpy(buf->list+from, repl);
796 }
797
798 struct string_list str_tab;
799
800 const char *
801 strtab(unsigned int ii) {
802     if (ii > 65536)
803         return NULL;
804     if (ii > str_tab.size) {
805         unsigned int old_size = str_tab.size;
806         while (ii >= str_tab.size)
807             str_tab.size <<= 1;
808         str_tab.list = realloc(str_tab.list, str_tab.size*sizeof(str_tab.list[0]));
809         memset(str_tab.list+old_size, 0, (str_tab.size-old_size)*sizeof(str_tab.list[0]));
810     }
811     if (!str_tab.list[ii]) {
812         str_tab.list[ii] = malloc(12);
813         sprintf(str_tab.list[ii], "%u", ii);
814     }
815     return str_tab.list[ii];
816 }
817
818 void
819 tools_init(void)
820 {
821     unsigned int upr, lwr;
822     for (lwr=0; lwr<256; ++lwr)
823         tolower(lwr) = lwr;
824     for (upr='A', lwr='a'; lwr <= 'z'; ++upr, ++lwr)
825         tolower(upr) = lwr;
826 #ifdef WITH_PROTOCOL_P10
827     for (upr='[', lwr='{'; lwr <= '~'; ++upr, ++lwr)
828         tolower(upr) = lwr;
829     for (upr=0xc0, lwr=0xe0; lwr <= 0xf6; ++upr, ++lwr)
830         tolower(upr) = lwr;
831     for (upr=0xd8, lwr=0xf8; lwr <= 0xfe; ++upr, ++lwr)
832         tolower(upr) = lwr;
833 #endif
834     str_tab.size = 1001;
835     str_tab.list = calloc(str_tab.size, sizeof(str_tab.list[0]));
836 }
837
838 void
839 tools_cleanup(void)
840 {
841     unsigned int ii;
842     for (ii=0; ii<str_tab.size; ++ii)
843         free(str_tab.list[ii]);
844     free(str_tab.list);
845 }