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