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