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