Fix match_ircglob() and globtest
[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     const char *m = glob, *n = text;
249     const char *m_tmp = glob, *n_tmp = text;
250     int star_p;
251
252     for (;;) switch (*m) {
253     case '\0':
254         if (!*n)
255             return 1;
256     backtrack:
257         if (m_tmp == glob)
258             return 0;
259         m = m_tmp;
260         n = ++n_tmp;
261         break;
262     case '\\':
263         m++;
264         /* allow escaping to force capitalization */
265         if (*m++ != *n++)
266             return 0;
267         break;
268     case '*': case '?':
269         for (star_p = 0; ; m++) {
270             if (*m == '*')
271                 star_p = 1;
272             else if (*m == '?') {
273                 if (!*n++)
274                     goto backtrack;
275             } else break;
276         }
277         if (star_p) {
278             if (!*m)
279                 return 1;
280             else if (*m == '\\') {
281                 m_tmp = ++m;
282                 if (!*m)
283                     return 0;
284                 for (n_tmp = n; *n && *n != *m; n++) ;
285             } else {
286                 m_tmp = m;
287                 for (n_tmp = n; *n && tolower(*n) != tolower(*m); n++) ;
288             }
289         }
290         /* and fall through */
291     default:
292         if (!*n)
293             return *m == '\0';
294         if (tolower(*m) != tolower(*n))
295             goto backtrack;
296         m++;
297         n++;
298         break;
299     }
300 }
301
302 extern const char *hidden_host_suffix;
303
304 int
305 user_matches_glob(struct userNode *user, const char *orig_glob, int include_nick)
306 {
307     char *glob, *marker;
308
309     /* Make a writable copy of the glob */
310     glob = alloca(strlen(orig_glob)+1);
311     strcpy(glob, orig_glob);
312     /* Check the nick, if it's present */
313     if (include_nick) {
314         if (!(marker = strchr(glob, '!'))) {
315             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);
316             return 0;
317         }
318         *marker = 0;
319         if (!match_ircglob(user->nick, glob)) return 0;
320         glob = marker + 1;
321     }
322     /* Check the ident */
323     if (!(marker = strchr(glob, '@'))) {
324         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);
325         return 0;
326     }
327     *marker = 0;
328     if (!match_ircglob(user->ident, glob))
329         return 0;
330     glob = marker + 1;
331     /* If it might be an IP glob, test that. */
332     if (!glob[strspn(glob, "0123456789./*?")]
333         && match_ircglob(inet_ntoa(user->ip), glob))
334         return 1;
335     /* Check for a fakehost match. */
336     if (IsFakeHost(user) && match_ircglob(user->fakehost, glob))
337             return 1;
338     /* Check for an account match. */
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     /* None of the above; could only be a hostname match. */
346     return match_ircglob(user->hostname, glob);
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 intervalString(char *output, time_t interval, struct handle_info *hi)
644 {
645     static const struct {
646         const char *msg_single;
647         const char *msg_plural;
648         long length;
649     } unit[] = {
650         { "MSG_YEAR",   "MSG_YEARS", 365 * 24 * 60 * 60 },
651         { "MSG_WEEK",   "MSG_WEEKS",   7 * 24 * 60 * 60 },
652         { "MSG_DAY",    "MSG_DAYS",        24 * 60 * 60 },
653         { "MSG_HOUR",   "MSG_HOURS",            60 * 60 },
654         { "MSG_MINUTE", "MSG_MINUTES",               60 },
655         { "MSG_SECOND", "MSG_SECONDS",                1 }
656     };
657     struct language *lang;
658     const char *msg;
659     unsigned int type, words, pos, count;
660
661     lang = hi ? hi->language : lang_C;
662     if(!interval)
663     {
664         msg = language_find_message(lang, "MSG_0_SECONDS");
665         return strcpy(output, msg);
666     }
667
668     for (type = 0, words = pos = 0;
669          interval && (words < 2) && (type < ArrayLength(unit));
670          type++) {
671         if (interval < unit[type].length)
672             continue;
673         count = interval / unit[type].length;
674         interval = interval % unit[type].length;
675
676         if (words++ == 1) {
677             msg = language_find_message(lang, "MSG_AND");
678             pos += sprintf(output + pos, " %s ", msg);
679         }
680         if (count == 1)
681             msg = language_find_message(lang, unit[type].msg_single);
682         else
683             msg = language_find_message(lang, unit[type].msg_plural);
684         pos += sprintf(output + pos, "%d %s", count, msg);
685     }
686
687     output[pos] = 0;
688     return output;
689 }
690
691 int
692 getipbyname(const char *name, unsigned long *ip)
693 {
694     struct hostent *he = gethostbyname(name);
695     if (!he)
696         return 0;
697     if (he->h_addrtype != AF_INET)
698         return 0;
699     memcpy(ip, he->h_addr_list[0], sizeof(*ip));
700     return 1;
701 }
702
703 DEFINE_LIST(string_buffer, char)
704
705 void
706 string_buffer_append_substring(struct string_buffer *buf, const char *tail, unsigned int len)
707 {
708     while (buf->used + len >= buf->size) {
709         if (!buf->size)
710             buf->size = 16;
711         else
712             buf->size <<= 1;
713         buf->list = realloc(buf->list, buf->size*sizeof(buf->list[0]));
714     }
715     memcpy(buf->list + buf->used, tail, len+1);
716     buf->used += len;
717 }
718
719 void
720 string_buffer_append_string(struct string_buffer *buf, const char *tail)
721 {
722     string_buffer_append_substring(buf, tail, strlen(tail));
723 }
724
725 void
726 string_buffer_append_vprintf(struct string_buffer *buf, const char *fmt, va_list args)
727 {
728     va_list working;
729     unsigned int len;
730     int ret;
731
732     VA_COPY(working, args);
733     len = strlen(fmt);
734     if (!buf->list || ((buf->used + buf->size) < len)) {
735         buf->size = buf->used + len;
736         buf->list = realloc(buf->list, buf->size);
737     }
738     ret = vsnprintf(buf->list + buf->used, buf->size - buf->used, fmt, working);
739     if (ret <= 0) {
740         /* pre-C99 behavior; double buffer size until it is big enough */
741         va_end(working);
742         VA_COPY(working, args);
743         while ((ret = vsnprintf(buf->list + buf->used, buf->size - buf->used, fmt, working)) <= 0) {
744             buf->size += len;
745             buf->list = realloc(buf->list, buf->size);
746             va_end(working);
747             VA_COPY(working, args);
748         }
749         buf->used += ret;
750     } else if (buf->used + ret < buf->size) {
751         /* no need to increase allocation size */
752         buf->used += ret;
753     } else {
754         /* now we know exactly how much space we need */
755         if (buf->size <= buf->used + ret) {
756             buf->size = buf->used + ret + 1;
757             buf->list = realloc(buf->list, buf->size);
758         }
759         va_end(working);
760         VA_COPY(working, args);
761         buf->used += vsnprintf(buf->list + buf->used, buf->size, fmt, working);
762     }
763     va_end(working);
764     va_end(args);
765 }
766
767 void string_buffer_append_printf(struct string_buffer *buf, const char *fmt, ...)
768 {
769     va_list args;
770     va_start(args, fmt);
771     string_buffer_append_vprintf(buf, fmt, args);
772 }
773
774 void
775 string_buffer_replace(struct string_buffer *buf, unsigned int from, unsigned int len, const char *repl)
776 {
777     unsigned int repl_len = strlen(repl);
778     if (from > buf->used)
779         return;
780     if (len + from > buf->used)
781         len = buf->used - from;
782     buf->used = buf->used + repl_len - len;
783     if (buf->size <= buf->used) {
784         while (buf->used >= buf->size)
785             buf->size <<= 1;
786         buf->list = realloc(buf->list, buf->size*sizeof(buf->list[0]));
787     }
788     memmove(buf->list+from+repl_len, buf->list+from+len, strlen(buf->list+from+len));
789     strcpy(buf->list+from, repl);
790 }
791
792 struct string_list str_tab;
793
794 const char *
795 strtab(unsigned int ii) {
796     if (ii > 65536)
797         return NULL;
798     if (ii > str_tab.size) {
799         unsigned int old_size = str_tab.size;
800         while (ii >= str_tab.size)
801             str_tab.size <<= 1;
802         str_tab.list = realloc(str_tab.list, str_tab.size*sizeof(str_tab.list[0]));
803         memset(str_tab.list+old_size, 0, (str_tab.size-old_size)*sizeof(str_tab.list[0]));
804     }
805     if (!str_tab.list[ii]) {
806         str_tab.list[ii] = malloc(12);
807         sprintf(str_tab.list[ii], "%u", ii);
808     }
809     return str_tab.list[ii];
810 }
811
812 void
813 tools_init(void)
814 {
815     unsigned int upr, lwr;
816     for (lwr=0; lwr<256; ++lwr)
817         tolower(lwr) = lwr;
818     for (upr='A', lwr='a'; lwr <= 'z'; ++upr, ++lwr)
819         tolower(upr) = lwr;
820 #ifdef WITH_PROTOCOL_P10
821     for (upr='[', lwr='{'; lwr <= '~'; ++upr, ++lwr)
822         tolower(upr) = lwr;
823     for (upr=0xc0, lwr=0xe0; lwr <= 0xf6; ++upr, ++lwr)
824         tolower(upr) = lwr;
825     for (upr=0xd8, lwr=0xf8; lwr <= 0xfe; ++upr, ++lwr)
826         tolower(upr) = lwr;
827 #endif
828     str_tab.size = 1001;
829     str_tab.list = calloc(str_tab.size, sizeof(str_tab.list[0]));
830 }
831
832 void
833 tools_cleanup(void)
834 {
835     unsigned int ii;
836     for (ii=0; ii<str_tab.size; ++ii)
837         free(str_tab.list[ii]);
838     free(str_tab.list);
839 }