IPv6 fixes around network routines.
[silc.git] / lib / silcutil / silcutil.c
1 /*
2
3   silcutil.c 
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 1997 - 2002 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19 /*
20  * These are general utility functions that doesn't belong to any specific
21  * group of routines.
22  */
23 /* $Id$ */
24
25 #include "silcincludes.h"
26
27 /* Gets line from a buffer. Stops reading when a newline or EOF occurs.
28    This doesn't remove the newline sign from the destination buffer. The
29    argument begin is returned and should be passed again for the function. */
30
31 int silc_gets(char *dest, int destlen, const char *src, int srclen, int begin)
32 {
33   static int start = 0;
34   int i;
35   
36   memset(dest, 0, destlen);
37   
38   if (begin != start)
39     start = 0;
40   
41   i = 0;
42   for ( ; start <= srclen; i++, start++) {
43     if (i > destlen)
44       return -1;
45     
46     dest[i] = src[start];
47     
48     if (dest[i] == EOF) 
49       return EOF;
50     
51     if (dest[i] == '\n') 
52       break;
53   }
54   start++;
55   
56   return start;
57 }
58
59 /* Checks line for illegal characters. Return -1 when illegal character
60    were found. This is used to check for bad lines when reading data from
61    for example a configuration file. */
62
63 int silc_check_line(char *buf) 
64 {
65   /* Illegal characters in line */
66   if (strchr(buf, '#')) return -1;
67   if (strchr(buf, '\'')) return -1;
68   if (strchr(buf, '\\')) return -1;
69   if (strchr(buf, '\r')) return -1;
70   if (strchr(buf, '\a')) return -1;
71   if (strchr(buf, '\b')) return -1;
72   if (strchr(buf, '\f')) return -1;
73   
74   /* Empty line */
75   if (buf[0] == '\n')
76     return -1;
77   
78   return 0;
79 }
80
81 /* Returns current time as string. */
82
83 char *silc_get_time()
84 {
85   time_t curtime;
86   char *return_time;
87
88   curtime = time(NULL);
89   return_time = ctime(&curtime);
90   return_time[strlen(return_time) - 1] = '\0';
91
92   return return_time;
93 }
94
95 /* Converts string to capital characters */
96
97 char *silc_to_upper(char *string)
98 {
99   int i;
100   char *ret = silc_calloc(strlen(string) + 1, sizeof(char));
101
102   for (i = 0; i < strlen(string); i++)
103     ret[i] = toupper(string[i]);
104
105   return ret;
106 }
107
108 static unsigned char pem_enc[64] =
109 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
110
111 /* Encodes data into PEM encoding. Returns NULL terminated PEM encoded
112    data string. Note: This is originally public domain code and is 
113    still PD. */
114
115 char *silc_encode_pem(unsigned char *data, SilcUInt32 len)
116 {
117   int i, j;
118   SilcUInt32 bits, c, char_count;
119   char *pem;
120
121   char_count = 0;
122   bits = 0;
123   j = 0;
124
125   pem = silc_calloc(((len * 8 + 5) / 6) + 5, sizeof(*pem));
126
127   for (i = 0; i < len; i++) {
128     c = data[i];
129     bits += c;
130     char_count++;
131
132     if (char_count == 3) {
133       pem[j++] = pem_enc[bits  >> 18];
134       pem[j++] = pem_enc[(bits >> 12) & 0x3f];
135       pem[j++] = pem_enc[(bits >> 6)  & 0x3f];
136       pem[j++] = pem_enc[bits & 0x3f];
137       bits = 0;
138       char_count = 0;
139     } else {
140       bits <<= 8;
141     }
142   }
143
144   if (char_count != 0) {
145     bits <<= 16 - (8 * char_count);
146     pem[j++] = pem_enc[bits >> 18];
147     pem[j++] = pem_enc[(bits >> 12) & 0x3f];
148
149     if (char_count == 1) {
150       pem[j++] = '=';
151       pem[j] = '=';
152     } else {
153       pem[j++] = pem_enc[(bits >> 6) & 0x3f];
154       pem[j] = '=';
155     }
156   }
157
158   return pem;
159 }
160
161 /* Same as above but puts newline ('\n') every 72 characters. */
162
163 char *silc_encode_pem_file(unsigned char *data, SilcUInt32 data_len)
164 {
165   int i, j;
166   SilcUInt32 len, cols;
167   char *pem, *pem2;
168
169   pem = silc_encode_pem(data, data_len);
170   len = strlen(pem);
171
172   pem2 = silc_calloc(len + (len / 72) + 1, sizeof(*pem2));
173
174   for (i = 0, j = 0, cols = 1; i < len; i++, cols++) {
175     if (cols == 72) {
176       pem2[i] = '\n';
177       cols = 0;
178       len++;
179       continue;
180     }
181
182     pem2[i] = pem[j++];
183   }
184
185   silc_free(pem);
186   return pem2;
187 }
188
189 /* Decodes PEM into data. Returns the decoded data. Note: This is
190    originally public domain code and is still PD. */
191
192 unsigned char *silc_decode_pem(unsigned char *pem, SilcUInt32 pem_len,
193                                SilcUInt32 *ret_len)
194 {
195   int i, j;
196   SilcUInt32 len, c, char_count, bits;
197   unsigned char *data;
198   static char ialpha[256], decoder[256];
199
200   for (i = 64 - 1; i >= 0; i--) {
201     ialpha[pem_enc[i]] = 1;
202     decoder[pem_enc[i]] = i;
203   }
204
205   char_count = 0;
206   bits = 0;
207   j = 0;
208
209   if (!pem_len)
210     len = strlen(pem);
211   else
212     len = pem_len;
213
214   data = silc_calloc(((len * 6) / 8), sizeof(*data));
215
216   for (i = 0; i < len; i++) {
217     c = pem[i];
218
219     if (c == '=')
220       break;
221
222     if (c > 127 || !ialpha[c])
223       continue;
224
225     bits += decoder[c];
226     char_count++;
227
228     if (char_count == 4) {
229       data[j++] = bits >> 16;
230       data[j++] = (bits >> 8) & 0xff;
231       data[j++] = bits & 0xff;
232       bits = 0;
233       char_count = 0;
234     } else {
235       bits <<= 6;
236     }
237   }
238
239   switch(char_count) {
240   case 1:
241     silc_free(data);
242     return NULL;
243     break;
244   case 2:
245     data[j++] = bits >> 10;
246     break;
247   case 3:
248     data[j++] = bits >> 16;
249     data[j++] = (bits >> 8) & 0xff;
250     break;
251   }
252
253   if (ret_len)
254     *ret_len = j;
255
256   return data;
257 }
258
259 /* Parse userfqdn string which is in user@fqdn format */
260
261 bool silc_parse_userfqdn(const char *string, char **left, char **right)
262 {
263   SilcUInt32 tlen;
264
265   if (!string)
266     return FALSE;
267
268   if (string[0] == '@') {
269     if (left)
270       *left = strdup(string);
271     return TRUE;
272   }
273
274   if (strchr(string, '@')) {
275     tlen = strcspn(string, "@");
276     
277     if (left) {
278       *left = silc_calloc(tlen + 1, sizeof(char));
279       memcpy(*left, string, tlen);
280     }
281     
282     if (right) {
283       *right = silc_calloc((strlen(string) - tlen) + 1, sizeof(char));
284       memcpy(*right, string + tlen + 1, strlen(string) - tlen - 1);
285     }
286   } else {
287     if (left)
288       *left = strdup(string);
289   }
290
291   return TRUE;
292 }
293
294 /* Parses command line. At most `max_args' is taken. Rest of the line
295    will be allocated as the last argument if there are more than `max_args'
296    arguments in the line. Note that the command name is counted as one
297    argument and is saved. */
298
299 void silc_parse_command_line(unsigned char *buffer, 
300                              unsigned char ***parsed,
301                              SilcUInt32 **parsed_lens,
302                              SilcUInt32 **parsed_types,
303                              SilcUInt32 *parsed_num,
304                              SilcUInt32 max_args)
305 {
306   int i, len = 0;
307   int argc = 0;
308   const char *cp = buffer;
309   char *tmp;
310
311   *parsed = silc_calloc(1, sizeof(**parsed));
312   *parsed_lens = silc_calloc(1, sizeof(**parsed_lens));
313
314   /* Get the command first */
315   len = strcspn(cp, " ");
316   tmp = silc_to_upper((char *)cp);
317   (*parsed)[0] = silc_calloc(len + 1, sizeof(char));
318   memcpy((*parsed)[0], tmp, len);
319   silc_free(tmp);
320   (*parsed_lens)[0] = len;
321   cp += len;
322   while (*cp == ' ')
323     cp++;
324   argc++;
325
326   /* Parse arguments */
327   if (strchr(cp, ' ') || strlen(cp) != 0) {
328     for (i = 1; i < max_args; i++) {
329
330       if (i != max_args - 1)
331         len = strcspn(cp, " ");
332       else
333         len = strlen(cp);
334       while (len && cp[len - 1] == ' ')
335         len--;
336       if (!len)
337         break;
338       
339       *parsed = silc_realloc(*parsed, sizeof(**parsed) * (argc + 1));
340       *parsed_lens = silc_realloc(*parsed_lens, 
341                                   sizeof(**parsed_lens) * (argc + 1));
342       (*parsed)[argc] = silc_calloc(len + 1, sizeof(char));
343       memcpy((*parsed)[argc], cp, len);
344       (*parsed_lens)[argc] = len;
345       argc++;
346
347       cp += len;
348       if (strlen(cp) == 0)
349         break;
350       else
351         while (*cp == ' ')
352           cp++;
353     }
354   }
355
356   /* Save argument types. Protocol defines all argument types but
357      this implementation makes sure that they are always in correct
358      order hence this simple code. */
359   *parsed_types = silc_calloc(argc, sizeof(**parsed_types));
360   for (i = 0; i < argc; i++)
361     (*parsed_types)[i] = i;
362
363   *parsed_num = argc;
364 }
365
366 /* Formats arguments to a string and returns it after allocating memory
367    for it. It must be remembered to free it later. */
368
369 char *silc_format(char *fmt, ...)
370 {
371   va_list args;
372   static char buf[8192];
373
374   memset(buf, 0, sizeof(buf));
375   va_start(args, fmt);
376   vsnprintf(buf, sizeof(buf) - 1, fmt, args);
377   va_end(args);
378
379   return strdup(buf);
380 }
381
382 /* Renders ID to suitable to print for example to log file. */
383
384 static char rid[256];
385
386 char *silc_id_render(void *id, SilcUInt16 type)
387 {
388   char tmp[100];
389   unsigned char tmps[2];
390
391   memset(rid, 0, sizeof(rid));
392   switch(type) {
393   case SILC_ID_SERVER:
394     {
395       SilcServerID *server_id = (SilcServerID *)id;
396       if (server_id->ip.data_len > 4) {
397 #ifdef HAVE_IPV6
398         struct sockaddr_in6 ipv6;
399         memset(&ipv6, 0, sizeof(ipv6));
400         ipv6.sin6_family = AF_INET6;
401         memmove(&ipv6.sin6_addr, server_id->ip.data, sizeof(ipv6.sin6_addr));
402         if (!getnameinfo((struct sockaddr *)&ipv6, sizeof(ipv6),
403                          tmp, sizeof(tmp), NULL, 0, NI_NUMERICHOST))
404           strcat(rid, tmp);
405 #endif
406       } else {
407         struct in_addr ipv4;
408         memmove(&ipv4.s_addr, server_id->ip.data, 4);
409         strcat(rid, inet_ntoa(ipv4));
410       }
411
412       memset(tmp, 0, sizeof(tmp));
413       snprintf(tmp, sizeof(tmp), ",%d,", ntohs(server_id->port));
414       strcat(rid, tmp);
415       SILC_PUT16_MSB(server_id->rnd, tmps);
416       memset(tmp, 0, sizeof(tmp));
417       snprintf(tmp, sizeof(tmp), "[%02x %02x]", tmps[0], tmps[1]);
418       strcat(rid, tmp);
419     }
420     break;
421   case SILC_ID_CLIENT:
422     {
423       SilcClientID *client_id = (SilcClientID *)id;
424       if (client_id->ip.data_len > 4) {
425 #ifdef HAVE_IPV6
426         struct sockaddr_in6 ipv6;
427         memset(&ipv6, 0, sizeof(ipv6));
428         ipv6.sin6_family = AF_INET6;
429         memmove(&ipv6.sin6_addr, client_id->ip.data, sizeof(ipv6.sin6_addr));
430         if (!getnameinfo((struct sockaddr *)&ipv6, sizeof(ipv6),
431                          tmp, sizeof(tmp), NULL, 0, NI_NUMERICHOST))
432           strcat(rid, tmp);
433 #endif
434       } else {
435         struct in_addr ipv4;
436         memmove(&ipv4.s_addr, client_id->ip.data, 4);
437         strcat(rid, inet_ntoa(ipv4));
438       }
439
440       memset(tmp, 0, sizeof(tmp));
441       snprintf(tmp, sizeof(tmp), ",%02x,", client_id->rnd);
442       strcat(rid, tmp);
443       memset(tmp, 0, sizeof(tmp));
444       snprintf(tmp, sizeof(tmp), "[%02x %02x %02x %02x...]", 
445                client_id->hash[0], client_id->hash[1],
446                client_id->hash[2], client_id->hash[3]);
447       strcat(rid, tmp);
448     }
449     break;
450   case SILC_ID_CHANNEL:
451     {
452       SilcChannelID *channel_id = (SilcChannelID *)id;
453       if (channel_id->ip.data_len > 4) {
454 #ifdef HAVE_IPV6
455         struct sockaddr_in6 ipv6;
456         memset(&ipv6, 0, sizeof(ipv6));
457         ipv6.sin6_family = AF_INET6;
458         memmove(&ipv6.sin6_addr, channel_id->ip.data, sizeof(ipv6.sin6_addr));
459         if (!getnameinfo((struct sockaddr *)&ipv6, sizeof(ipv6),
460                          tmp, sizeof(tmp), NULL, 0, NI_NUMERICHOST))
461           strcat(rid, tmp);
462 #endif
463       } else {
464         struct in_addr ipv4;
465         memmove(&ipv4.s_addr, channel_id->ip.data, 4);
466         strcat(rid, inet_ntoa(ipv4));
467       }
468
469       memset(tmp, 0, sizeof(tmp));
470       snprintf(tmp, sizeof(tmp), ",%d,", ntohs(channel_id->port));
471       strcat(rid, tmp);
472       SILC_PUT16_MSB(channel_id->rnd, tmps);
473       memset(tmp, 0, sizeof(tmp));
474       snprintf(tmp, sizeof(tmp), "[%02x %02x]", tmps[0], tmps[1]);
475       strcat(rid, tmp);
476     }
477     break;
478   }
479
480   return rid;
481 }
482
483 /* Compares two strings. Strings may include wildcards * and ?.
484    Returns TRUE if strings match. */
485
486 int silc_string_compare(char *string1, char *string2)
487 {
488   int i;
489   int slen1;
490   int slen2;
491   char *tmpstr1, *tmpstr2;
492
493   if (!string1 || !string2)
494     return FALSE;
495
496   slen1 = strlen(string1);
497   slen2 = strlen(string2);
498
499   /* See if they are same already */
500   if (!strncmp(string1, string2, strlen(string2)))
501     return TRUE;
502
503   if (slen2 < slen1)
504     if (!strchr(string1, '*'))
505       return FALSE;
506   
507   /* Take copies of the original strings as we will change them */
508   tmpstr1 = silc_calloc(slen1 + 1, sizeof(char));
509   memcpy(tmpstr1, string1, slen1);
510   tmpstr2 = silc_calloc(slen2 + 1, sizeof(char));
511   memcpy(tmpstr2, string2, slen2);
512   
513   for (i = 0; i < slen1; i++) {
514     
515     /* * wildcard. Only one * wildcard is possible. */
516     if (tmpstr1[i] == '*')
517       if (!strncmp(tmpstr1, tmpstr2, i)) {
518         memset(tmpstr2, 0, slen2);
519         strncpy(tmpstr2, tmpstr1, i);
520         break;
521       }
522     
523     /* ? wildcard */
524     if (tmpstr1[i] == '?') {
525       if (!strncmp(tmpstr1, tmpstr2, i)) {
526         if (!(slen1 < i + 1))
527           if (tmpstr1[i + 1] != '?' &&
528               tmpstr1[i + 1] != tmpstr2[i + 1])
529             continue;
530         
531         if (!(slen1 < slen2))
532           tmpstr2[i] = '?';
533       }
534     }
535   }
536   
537   /* if using *, remove it */
538   if (strchr(tmpstr1, '*'))
539     *strchr(tmpstr1, '*') = 0;
540   
541   if (!strcmp(tmpstr1, tmpstr2)) {
542     memset(tmpstr1, 0, slen1);
543     memset(tmpstr2, 0, slen2);
544     silc_free(tmpstr1);
545     silc_free(tmpstr2);
546     return TRUE;
547   }
548   
549   memset(tmpstr1, 0, slen1);
550   memset(tmpstr2, 0, slen2);
551   silc_free(tmpstr1);
552   silc_free(tmpstr2);
553   return FALSE;
554 }
555
556 /* Basic has function to hash strings. May be used with the SilcHashTable. 
557    Note that this lowers the characters of the string (with tolower()) so
558    this is used usually with nicknames, channel and server names to provide
559    case insensitive keys. */
560
561 SilcUInt32 silc_hash_string(void *key, void *user_context)
562 {
563   char *s = (char *)key;
564   SilcUInt32 h = 0, g;
565   
566   while (*s != '\0') {
567     h = (h << 4) + tolower(*s);
568     if ((g = h & 0xf0000000)) {
569       h = h ^ (g >> 24);
570       h = h ^ g;
571     }
572     s++;
573   }
574   
575   return h;
576 }
577
578 /* Basic hash function to hash integers. May be used with the SilcHashTable. */
579
580 SilcUInt32 silc_hash_uint(void *key, void *user_context)
581 {
582   return *(SilcUInt32 *)key;
583 }
584
585 /* Basic hash funtion to hash pointers. May be used with the SilcHashTable. */
586
587 SilcUInt32 silc_hash_ptr(void *key, void *user_context)
588 {
589   return (SilcUInt32)key;
590 }
591
592 /* Hash a ID. The `user_context' is the ID type. */
593
594 SilcUInt32 silc_hash_id(void *key, void *user_context)
595 {
596   SilcIdType id_type = (SilcIdType)(SilcUInt32)user_context;
597   SilcUInt32 h = 0;
598   int i;
599
600   switch (id_type) {
601   case SILC_ID_CLIENT:
602     {
603       SilcClientID *id = (SilcClientID *)key;
604       SilcUInt32 g;
605   
606       /* The client ID is hashed by hashing the hash of the ID 
607          (which is a truncated MD5 hash of the nickname) so that we
608          can access the entry from the cache with both Client ID but
609          with just a hash from the ID as well. */
610
611       for (i = 0; i < sizeof(id->hash); i++) {
612         h = (h << 4) + id->hash[i];
613         if ((g = h & 0xf0000000)) {
614           h = h ^ (g >> 24);
615           h = h ^ g;
616         }
617       }
618
619       return h;
620     }
621     break;
622   case SILC_ID_SERVER:
623     {
624       SilcServerID *id = (SilcServerID *)key;
625       
626       h = id->port * id->rnd;
627       for (i = 0; i < id->ip.data_len; i++)
628         h ^= id->ip.data[i];
629       
630       return h;
631     }
632     break;
633   case SILC_ID_CHANNEL:
634     {
635       SilcChannelID *id = (SilcChannelID *)key;
636       
637       h = id->port * id->rnd;
638       for (i = 0; i < id->ip.data_len; i++)
639         h ^= id->ip.data[i];
640       
641       return h;
642     }
643     break;
644   default:
645     break;
646   }
647
648   return h;
649 }
650
651 /* Hash binary data. The `user_context' is the data length. */
652
653 SilcUInt32 silc_hash_data(void *key, void *user_context)
654 {
655   SilcUInt32 len = (SilcUInt32)user_context, h = 0;
656   unsigned char *data = (unsigned char *)key;
657   int i;
658
659   h = (data[0] * data[len - 1] + 1) * len;
660   for (i = 0; i < len; i++)
661     h ^= data[i];
662
663   return h;
664 }
665
666 /* Hashed SILC Public key. */
667
668 SilcUInt32 silc_hash_public_key(void *key, void *user_context)
669 {
670   SilcPublicKey pk = (SilcPublicKey)key;
671   return (pk->len + silc_hash_string(pk->name, NULL) +
672           silc_hash_string(pk->identifier, NULL) +
673           silc_hash_data(pk->pk, (void *)pk->pk_len));
674 }
675
676 /* Compares two strings. May be used as SilcHashTable comparison function. */
677
678 bool silc_hash_string_compare(void *key1, void *key2, void *user_context)
679 {
680   return !strcasecmp((char *)key1, (char *)key2);
681 }
682
683 /* Compares two ID's. May be used as SilcHashTable comparison function. 
684    The Client ID's compares only the hash of the Client ID not any other
685    part of the Client ID. Other ID's are fully compared. */
686
687 bool silc_hash_id_compare(void *key1, void *key2, void *user_context)
688 {
689   SilcIdType id_type = (SilcIdType)(SilcUInt32)user_context;
690   return (id_type == SILC_ID_CLIENT ? 
691           SILC_ID_COMPARE_HASH((SilcClientID *)key1, (SilcClientID *)key2) :
692           SILC_ID_COMPARE_TYPE(key1, key2, id_type));
693 }
694
695 /* Compare two Client ID's entirely and not just the hash from the ID. */
696
697 bool silc_hash_client_id_compare(void *key1, void *key2, void *user_context)
698 {
699   return SILC_ID_COMPARE_TYPE(key1, key2, SILC_ID_CLIENT);
700 }
701
702 /* Compares binary data. May be used as SilcHashTable comparison function. */
703
704 bool silc_hash_data_compare(void *key1, void *key2, void *user_context)
705 {
706   SilcUInt32 len = (SilcUInt32)user_context;
707   return !memcmp(key1, key2, len);
708 }
709
710 /* Compares two SILC Public keys. May be used as SilcHashTable comparison
711    function. */
712
713 bool silc_hash_public_key_compare(void *key1, void *key2, void *user_context)
714 {
715   return silc_pkcs_public_key_compare(key1, key2);
716 }
717
718 /* Parses mode mask and returns the mode as string. */
719
720 char *silc_client_chmode(SilcUInt32 mode, const char *cipher, const char *hmac)
721 {
722   char string[100];
723
724   if (!mode)
725     return NULL;
726
727   memset(string, 0, sizeof(string));
728
729   if (mode & SILC_CHANNEL_MODE_PRIVATE)
730     strncat(string, "p", 1);
731
732   if (mode & SILC_CHANNEL_MODE_SECRET)
733     strncat(string, "s", 1);
734
735   if (mode & SILC_CHANNEL_MODE_PRIVKEY)
736     strncat(string, "k", 1);
737
738   if (mode & SILC_CHANNEL_MODE_INVITE)
739     strncat(string, "i", 1);
740
741   if (mode & SILC_CHANNEL_MODE_TOPIC)
742     strncat(string, "t", 1);
743
744   if (mode & SILC_CHANNEL_MODE_ULIMIT)
745     strncat(string, "l", 1);
746
747   if (mode & SILC_CHANNEL_MODE_PASSPHRASE)
748     strncat(string, "a", 1);
749
750   if (mode & SILC_CHANNEL_MODE_FOUNDER_AUTH)
751     strncat(string, "f", 1);
752
753   if (mode & SILC_CHANNEL_MODE_CIPHER)
754     strncat(string, cipher, strlen(cipher));
755
756   if (mode & SILC_CHANNEL_MODE_HMAC)
757     strncat(string, hmac, strlen(hmac));
758
759   /* Rest of mode is ignored */
760
761   return strdup(string);
762 }
763
764 /* Parses channel user mode mask and returns te mode as string */
765
766 char *silc_client_chumode(SilcUInt32 mode)
767 {
768   char string[4];
769
770   if (!mode)
771     return NULL;
772
773   memset(string, 0, sizeof(string));
774
775   if (mode & SILC_CHANNEL_UMODE_CHANFO)
776     strncat(string, "f", 1);
777
778   if (mode & SILC_CHANNEL_UMODE_CHANOP)
779     strncat(string, "o", 1);
780
781   return strdup(string);
782 }
783
784 /* Parses channel user mode and returns it as special mode character. */
785
786 char *silc_client_chumode_char(SilcUInt32 mode)
787 {
788   char string[4];
789
790   if (!mode)
791     return NULL;
792
793   memset(string, 0, sizeof(string));
794
795   if (mode & SILC_CHANNEL_UMODE_CHANFO)
796     strncat(string, "*", 1);
797
798   if (mode & SILC_CHANNEL_UMODE_CHANOP)
799     strncat(string, "@", 1);
800
801   return strdup(string);
802 }
803
804 /* Creates fingerprint from data, usually used with SHA1 digests */
805
806 char *silc_fingerprint(const unsigned char *data, SilcUInt32 data_len)
807 {
808   char fingerprint[64], *cp;
809   int i;
810
811   memset(fingerprint, 0, sizeof(fingerprint));
812   cp = fingerprint;
813   for (i = 0; i < data_len; i++) {
814     snprintf(cp, sizeof(fingerprint), "%02X", data[i]);
815     cp += 2;
816     
817     if ((i + 1) % 2 == 0)
818       snprintf(cp++, sizeof(fingerprint), " ");
819
820     if ((i + 1) % 10 == 0)
821       snprintf(cp++, sizeof(fingerprint), " ");
822   }
823   i--;
824   if ((i + 1) % 2 == 0)
825     cp[-2] = 0;
826   if ((i + 1) % 10 == 0)
827     cp[-1] = 0;
828   
829   return strdup(fingerprint);
830 }