translate interval strings; translate more NickServ messages
[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 (hidden_host_suffix && user->handle_info) {
344             char hidden_host[HOSTLEN+1];
345             snprintf(hidden_host, sizeof(hidden_host), "%s.%s", user->handle_info->handle, hidden_host_suffix);
346             if (match_ircglob(hidden_host, glob))
347                 return 1;
348         }
349         return match_ircglob(user->hostname, glob);
350     }
351 }
352
353 int
354 is_ircmask(const char *text)
355 {
356     while (*text && (isalnum((char)*text) || strchr("-_[]|\\`^{}?*", *text)))
357         text++;
358     if (*text++ != '!')
359         return 0;
360     while (*text && *text != '@' && !isspace((char)*text))
361         text++;
362     if (*text++ != '@')
363         return 0;
364     while (*text && !isspace((char)*text))
365         text++;
366     return !*text;
367 }
368
369 int
370 is_gline(const char *text)
371 {
372     if (*text == '@')
373         return 0;
374     text += strcspn(text, "@!% \t\r\n");
375     if (*text++ != '@')
376         return 0;
377     if (!*text)
378         return 0;
379     while (*text && (isalnum((char)*text) || strchr(".-?*", *text)))
380         text++;
381     return !*text;
382 }
383
384 int
385 split_ircmask(char *text, char **nick, char **ident, char **host)
386 {
387     char *start;
388
389     start = text;
390     while (isalnum((char)*text) || strchr("=[]\\`^{}?*", *text))
391         text++;
392     if (*text != '!' || ((text - start) > NICKLEN))
393         return 0;
394     *text = 0;
395     if (nick)
396         *nick = start;
397
398     start = ++text;
399     while (*text && *text != '@' && !isspace((char)*text))
400         text++;
401     if (*text != '@' || ((text - start) > USERLEN))
402         return 0;
403     *text = 0;
404     if (ident)
405         *ident = start;
406     
407     start = ++text;
408     while (*text && (isalnum((char)*text) || strchr(".-?*", *text)))
409         text++;
410     if (host)
411         *host = start;
412     return !*text && ((text - start) <= HOSTLEN) && nick && ident && host;
413 }
414
415 char *
416 sanitize_ircmask(char *input)
417 {
418     unsigned int length, flag;
419     char *mask, *start, *output;
420
421     /* Sanitize everything in place; input *must* be a valid
422        hostmask. */
423     output = input;
424     flag = 0;
425
426     /* The nick is truncated at the end. */
427     length = 0;
428     mask = input;
429     while(*input++ != '!')
430     {
431         length++;
432     }
433     if(length > NICKLEN)
434     {
435         mask += NICKLEN;
436         *mask++ = '!';
437
438         /* This flag is used to indicate following parts should
439            be shifted. */
440         flag = 1;
441     }
442     else
443     {
444         mask = input;
445     }
446
447     /* The ident and host must be truncated at the beginning and
448        replaced with a '*' to be compatible with ircu. */
449     length = 0;
450     start = input;
451     while(*input++ != '@')
452     {
453         length++;
454     }
455     if(length > USERLEN || flag)
456     {
457         if(length > USERLEN)
458         {
459             start = input - USERLEN;
460             *mask++ = '*';
461         }
462         while(*start != '@')
463         {
464             *mask++ = *start++;
465         }
466         *mask++ = '@';
467
468         flag = 1;
469     }
470     else
471     {
472         mask = input;
473     }
474
475     length = 0;
476     start = input;
477     while(*input++)
478     {
479         length++;
480     }
481     if(length > HOSTLEN || flag)
482     {
483         if(length > HOSTLEN)
484         {
485             start = input - HOSTLEN;
486             *mask++ = '*';
487         }
488         while(*start)
489         {
490             *mask++ = *start++;
491         }
492         *mask = '\0';
493     }
494
495     return output;
496 }
497
498 static long
499 TypeLength(char type)
500 {
501     switch (type) {
502     case 'y': return 365*24*60*60;
503     case 'M': return 31*24*60*60;
504     case 'w': return 7*24*60*60;
505     case 'd': return 24*60*60;
506     case 'h': return 60*60;
507     case 'm': return 60;
508     case 's': return 1;
509     default: return 0;
510     }
511 }
512
513 unsigned long
514 ParseInterval(const char *interval)
515 {
516     unsigned long seconds = 0;
517     int partial = 0;
518     char c;
519
520     /* process the string, resetting the count if we find a unit character */
521     while ((c = *interval++)) {
522         if (isdigit((int)c)) {
523             partial = partial*10 + c - '0';
524         } else {
525             seconds += TypeLength(c) * partial;
526             partial = 0;
527         }
528     }
529     /* assume the last chunk is seconds (the normal case) */
530     return seconds + partial;
531 }
532
533 static long
534 GetSizeMultiplier(char type)
535 {
536     switch (type) {
537     case 'G': case 'g': return 1024*1024*1024;
538     case 'M': case 'm': return 1024*1024;
539     case 'K': case 'k': return 1024;
540     case 'B': case 'b': return 1;
541     default: return 0;
542     }
543 }
544
545 unsigned long
546 ParseVolume(const char *volume)
547 {
548     unsigned long accum = 0, partial = 0;
549     char c;
550     while ((c = *volume++)) {
551         if (isdigit((int)c)) {
552             partial = partial*10 + c - '0';
553         } else {
554             accum += GetSizeMultiplier(c) * partial;
555             partial = 0;
556         }
557     }
558     return accum + partial;
559 }
560
561 int
562 parse_ipmask(const char *str, struct in_addr *addr, unsigned long *mask)
563 {
564     int accum, pos;
565     unsigned long t_a, t_m;
566
567     t_a = t_m = pos = 0;
568     if (addr)
569         addr->s_addr = htonl(t_a);
570     if (mask)
571         *mask = t_m;
572     while (*str) {
573         if (!isdigit(*str))
574             return 0;
575         accum = 0;
576         do {
577             accum = (accum * 10) + *str++ - '0';
578         } while (isdigit(*str));
579         if (accum > 255)
580             return 0;
581         t_a = (t_a << 8) | accum;
582         t_m = (t_m << 8) | 255;
583         pos += 8;
584         if (*str == '.') {
585             str++;
586             while (*str == '*') {
587                 str++;
588                 if (*str == '.') {
589                     t_a <<= 8;
590                     t_m <<= 8;
591                     pos += 8;
592                     str++;
593                 } else if (*str == 0) {
594                     t_a <<= 32 - pos;
595                     t_m <<= 32 - pos;
596                     pos = 32;
597                     goto out;
598                 } else
599                     return 0;
600             }
601         } else if (*str == '/') {
602             int start = pos;
603             accum = 0;
604             do {
605                 accum = (accum * 10) + *str++ - '0';
606             } while (isdigit(*str));
607             while (pos < start+accum && pos < 32) {
608                 t_a = (t_a << 1) | 0;
609                 t_m = (t_m << 1) | 1;
610                 pos++;
611             }
612             if (pos != start+accum)
613                 return 0;
614         } else if (*str == 0)
615             break;
616         else
617             return 0;
618     }
619 out:
620     if (pos != 32)
621         return 0;
622     if (addr)
623         addr->s_addr = htonl(t_a);
624     if (mask)
625         *mask = t_m;
626     return 1;
627 }
628
629 char *
630 unsplit_string(char *set[], unsigned int max, char *dest)
631 {
632     static char unsplit_buffer[MAXLEN*2];
633     unsigned int ii, jj, pos;
634
635     if (!dest)
636         dest = unsplit_buffer;
637     for (ii=pos=0; ii<max; ii++) {
638         for (jj=0; set[ii][jj]; jj++)
639             dest[pos++] = set[ii][jj];
640         dest[pos++] = ' ';
641     }
642     dest[--pos] = 0;
643     return dest;
644 }
645
646 char *
647 intervalString(char *output, time_t interval, struct handle_info *hi)
648 {
649     static const struct {
650         const char *msg_single;
651         const char *msg_plural;
652         long length;
653     } unit[] = {
654         { "MSG_YEAR",   "MSG_YEARS", 365 * 24 * 60 * 60 },
655         { "MSG_WEEK",   "MSG_WEEKS",   7 * 24 * 60 * 60 },
656         { "MSG_DAY",    "MSG_DAYS",        24 * 60 * 60 },
657         { "MSG_HOUR",   "MSG_HOURS",            60 * 60 },
658         { "MSG_MINUTE", "MSG_MINUTES",               60 },
659         { "MSG_SECOND", "MSG_SECONDS",                1 }
660     };
661     struct language *lang;
662     const char *msg;
663     unsigned int type, words, pos, count;
664
665     lang = hi ? hi->language : lang_C;
666     if(!interval)
667     {
668         msg = language_find_message(lang, "MSG_0_SECONDS");
669         return strcpy(output, msg);
670     }
671
672     for (type = 0, words = pos = 0;
673          interval && (words < 2) && (type < ArrayLength(unit));
674          type++) {
675         if (interval < unit[type].length)
676             continue;
677         count = interval / unit[type].length;
678         interval = interval % unit[type].length;
679
680         if (words++ == 1) {
681             msg = language_find_message(lang, "MSG_AND");
682             pos += sprintf(output + pos, " %s ", msg);
683         }
684         if (count == 1)
685             msg = language_find_message(lang, unit[type].msg_single);
686         else
687             msg = language_find_message(lang, unit[type].msg_plural);
688         pos += sprintf(output + pos, "%d %s", count, msg);
689     }
690
691     output[pos] = 0;
692     return output;
693 }
694
695 int
696 getipbyname(const char *name, unsigned long *ip)
697 {
698     struct hostent *he = gethostbyname(name);
699     if (!he)
700         return 0;
701     if (he->h_addrtype != AF_INET)
702         return 0;
703     memcpy(ip, he->h_addr_list[0], sizeof(*ip));
704     return 1;
705 }
706
707 DEFINE_LIST(string_buffer, char)
708
709 void
710 string_buffer_append_substring(struct string_buffer *buf, const char *tail, unsigned int len)
711 {
712     while (buf->used + len >= buf->size) {
713         if (!buf->size)
714             buf->size = 16;
715         else
716             buf->size <<= 1;
717         buf->list = realloc(buf->list, buf->size*sizeof(buf->list[0]));
718     }
719     memcpy(buf->list + buf->used, tail, len+1);
720     buf->used += len;
721 }
722
723 void
724 string_buffer_append_string(struct string_buffer *buf, const char *tail)
725 {
726     string_buffer_append_substring(buf, tail, strlen(tail));
727 }
728
729 void
730 string_buffer_append_vprintf(struct string_buffer *buf, const char *fmt, va_list args)
731 {
732     va_list working;
733     unsigned int len;
734     int ret;
735
736     VA_COPY(working, args);
737     len = strlen(fmt);
738     if (!buf->list || ((buf->used + buf->size) < len)) {
739         buf->size = buf->used + len;
740         buf->list = realloc(buf->list, buf->size);
741     }
742     ret = vsnprintf(buf->list + buf->used, buf->size - buf->used, fmt, working);
743     if (ret <= 0) {
744         /* pre-C99 behavior; double buffer size until it is big enough */
745         va_end(working);
746         VA_COPY(working, args);
747         while ((ret = vsnprintf(buf->list + buf->used, buf->size, fmt, working)) == -1) {
748             buf->size += len;
749             buf->list = realloc(buf->list, buf->size);
750             va_end(working);
751             VA_COPY(working, args);
752         }
753         buf->used += ret;
754     } else if (buf->used + ret < buf->size) {
755         /* no need to increase allocation size */
756         buf->used += ret;
757     } else {
758         /* now we know exactly how much space we need */
759         if (buf->size <= buf->used + ret) {
760             buf->size = buf->used + ret + 1;
761             buf->list = realloc(buf->list, buf->size);
762         }
763         va_end(working);
764         VA_COPY(working, args);
765         buf->used += vsnprintf(buf->list + buf->used, buf->size, fmt, working);
766     }
767     va_end(working);
768     va_end(args);
769 }
770
771 void string_buffer_append_printf(struct string_buffer *buf, const char *fmt, ...)
772 {
773     va_list args;
774     va_start(args, fmt);
775     string_buffer_append_vprintf(buf, fmt, args);
776 }
777
778 void
779 string_buffer_replace(struct string_buffer *buf, unsigned int from, unsigned int len, const char *repl)
780 {
781     unsigned int repl_len = strlen(repl);
782     if (from > buf->used)
783         return;
784     if (len + from > buf->used)
785         len = buf->used - from;
786     buf->used = buf->used + repl_len - len;
787     if (buf->size <= buf->used) {
788         while (buf->used >= buf->size)
789             buf->size <<= 1;
790         buf->list = realloc(buf->list, buf->size*sizeof(buf->list[0]));
791     }
792     memmove(buf->list+from+repl_len, buf->list+from+len, strlen(buf->list+from+len));
793     strcpy(buf->list+from, repl);
794 }
795
796 struct string_list str_tab;
797
798 const char *
799 strtab(unsigned int ii) {
800     if (ii > 65536)
801         return NULL;
802     if (ii > str_tab.size) {
803         unsigned int old_size = str_tab.size;
804         while (ii >= str_tab.size)
805             str_tab.size <<= 1;
806         str_tab.list = realloc(str_tab.list, str_tab.size*sizeof(str_tab.list[0]));
807         memset(str_tab.list+old_size, 0, (str_tab.size-old_size)*sizeof(str_tab.list[0]));
808     }
809     if (!str_tab.list[ii]) {
810         str_tab.list[ii] = malloc(12);
811         sprintf(str_tab.list[ii], "%u", ii);
812     }
813     return str_tab.list[ii];
814 }
815
816 void
817 tools_init(void)
818 {
819     unsigned int upr, lwr;
820     for (lwr=0; lwr<256; ++lwr)
821         tolower(lwr) = lwr;
822     for (upr='A', lwr='a'; lwr <= 'z'; ++upr, ++lwr)
823         tolower(upr) = lwr;
824 #ifdef WITH_PROTOCOL_P10
825     for (upr='[', lwr='{'; lwr <= '~'; ++upr, ++lwr)
826         tolower(upr) = lwr;
827     for (upr=0xc0, lwr=0xe0; lwr <= 0xf6; ++upr, ++lwr)
828         tolower(upr) = lwr;
829     for (upr=0xd8, lwr=0xf8; lwr <= 0xfe; ++upr, ++lwr)
830         tolower(upr) = lwr;
831 #endif
832     str_tab.size = 1001;
833     str_tab.list = calloc(str_tab.size, sizeof(str_tab.list[0]));
834 }
835
836 void
837 tools_cleanup(void)
838 {
839     unsigned int ii;
840     for (ii=0; ii<str_tab.size; ++ii)
841         free(str_tab.list[ii]);
842     free(str_tab.list);
843 }