Integer type name change.
[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 in6_addr ipv6;
399         memmove(&ipv6, server_id->ip.data, sizeof(ipv6));
400         if (!inet_ntop(AF_INET6, &ipv6, tmp, sizeof(tmp)))
401           strcat(rid, tmp);
402 #endif
403       } else {
404         struct in_addr ipv4;
405         memmove(&ipv4.s_addr, server_id->ip.data, 4);
406         strcat(rid, inet_ntoa(ipv4));
407       }
408
409       memset(tmp, 0, sizeof(tmp));
410       snprintf(tmp, sizeof(tmp), ",%d,", ntohs(server_id->port));
411       strcat(rid, tmp);
412       SILC_PUT16_MSB(server_id->rnd, tmps);
413       memset(tmp, 0, sizeof(tmp));
414       snprintf(tmp, sizeof(tmp), "[%02x %02x]", tmps[0], tmps[1]);
415       strcat(rid, tmp);
416     }
417     break;
418   case SILC_ID_CLIENT:
419     {
420       SilcClientID *client_id = (SilcClientID *)id;
421       if (client_id->ip.data_len > 4) {
422 #ifdef HAVE_IPV6
423         struct in6_addr ipv6;
424         memmove(&ipv6, client_id->ip.data, sizeof(ipv6));
425         if (!inet_ntop(AF_INET6, &ipv6, tmp, sizeof(tmp)))
426           strcat(rid, tmp);
427 #endif
428       } else {
429         struct in_addr ipv4;
430         memmove(&ipv4.s_addr, client_id->ip.data, 4);
431         strcat(rid, inet_ntoa(ipv4));
432       }
433
434       memset(tmp, 0, sizeof(tmp));
435       snprintf(tmp, sizeof(tmp), ",%02x,", client_id->rnd);
436       strcat(rid, tmp);
437       memset(tmp, 0, sizeof(tmp));
438       snprintf(tmp, sizeof(tmp), "[%02x %02x %02x %02x...]", 
439                client_id->hash[0], client_id->hash[1],
440                client_id->hash[2], client_id->hash[3]);
441       strcat(rid, tmp);
442     }
443     break;
444   case SILC_ID_CHANNEL:
445     {
446       SilcChannelID *channel_id = (SilcChannelID *)id;
447       if (channel_id->ip.data_len > 4) {
448 #ifdef HAVE_IPV6
449         struct in6_addr ipv6;
450         memmove(&ipv6, channel_id->ip.data, sizeof(ipv6));
451         if (!inet_ntop(AF_INET6, &ipv6, tmp, sizeof(tmp)))
452           strcat(rid, tmp);
453 #endif
454       } else {
455         struct in_addr ipv4;
456         memmove(&ipv4.s_addr, channel_id->ip.data, 4);
457         strcat(rid, inet_ntoa(ipv4));
458       }
459
460       memset(tmp, 0, sizeof(tmp));
461       snprintf(tmp, sizeof(tmp), ",%d,", ntohs(channel_id->port));
462       strcat(rid, tmp);
463       SILC_PUT16_MSB(channel_id->rnd, tmps);
464       memset(tmp, 0, sizeof(tmp));
465       snprintf(tmp, sizeof(tmp), "[%02x %02x]", tmps[0], tmps[1]);
466       strcat(rid, tmp);
467     }
468     break;
469   }
470
471   return rid;
472 }
473
474 /* Compares two strings. Strings may include wildcards * and ?.
475    Returns TRUE if strings match. */
476
477 int silc_string_compare(char *string1, char *string2)
478 {
479   int i;
480   int slen1;
481   int slen2;
482   char *tmpstr1, *tmpstr2;
483
484   if (!string1 || !string2)
485     return FALSE;
486
487   slen1 = strlen(string1);
488   slen2 = strlen(string2);
489
490   /* See if they are same already */
491   if (!strncmp(string1, string2, strlen(string2)))
492     return TRUE;
493
494   if (slen2 < slen1)
495     if (!strchr(string1, '*'))
496       return FALSE;
497   
498   /* Take copies of the original strings as we will change them */
499   tmpstr1 = silc_calloc(slen1 + 1, sizeof(char));
500   memcpy(tmpstr1, string1, slen1);
501   tmpstr2 = silc_calloc(slen2 + 1, sizeof(char));
502   memcpy(tmpstr2, string2, slen2);
503   
504   for (i = 0; i < slen1; i++) {
505     
506     /* * wildcard. Only one * wildcard is possible. */
507     if (tmpstr1[i] == '*')
508       if (!strncmp(tmpstr1, tmpstr2, i)) {
509         memset(tmpstr2, 0, slen2);
510         strncpy(tmpstr2, tmpstr1, i);
511         break;
512       }
513     
514     /* ? wildcard */
515     if (tmpstr1[i] == '?') {
516       if (!strncmp(tmpstr1, tmpstr2, i)) {
517         if (!(slen1 < i + 1))
518           if (tmpstr1[i + 1] != '?' &&
519               tmpstr1[i + 1] != tmpstr2[i + 1])
520             continue;
521         
522         if (!(slen1 < slen2))
523           tmpstr2[i] = '?';
524       }
525     }
526   }
527   
528   /* if using *, remove it */
529   if (strchr(tmpstr1, '*'))
530     *strchr(tmpstr1, '*') = 0;
531   
532   if (!strcmp(tmpstr1, tmpstr2)) {
533     memset(tmpstr1, 0, slen1);
534     memset(tmpstr2, 0, slen2);
535     silc_free(tmpstr1);
536     silc_free(tmpstr2);
537     return TRUE;
538   }
539   
540   memset(tmpstr1, 0, slen1);
541   memset(tmpstr2, 0, slen2);
542   silc_free(tmpstr1);
543   silc_free(tmpstr2);
544   return FALSE;
545 }
546
547 /* Basic has function to hash strings. May be used with the SilcHashTable. 
548    Note that this lowers the characters of the string (with tolower()) so
549    this is used usually with nicknames, channel and server names to provide
550    case insensitive keys. */
551
552 SilcUInt32 silc_hash_string(void *key, void *user_context)
553 {
554   char *s = (char *)key;
555   SilcUInt32 h = 0, g;
556   
557   while (*s != '\0') {
558     h = (h << 4) + tolower(*s);
559     if ((g = h & 0xf0000000)) {
560       h = h ^ (g >> 24);
561       h = h ^ g;
562     }
563     s++;
564   }
565   
566   return h;
567 }
568
569 /* Basic hash function to hash integers. May be used with the SilcHashTable. */
570
571 SilcUInt32 silc_hash_uint(void *key, void *user_context)
572 {
573   return *(SilcUInt32 *)key;
574 }
575
576 /* Basic hash funtion to hash pointers. May be used with the SilcHashTable. */
577
578 SilcUInt32 silc_hash_ptr(void *key, void *user_context)
579 {
580   return (SilcUInt32)key;
581 }
582
583 /* Hash a ID. The `user_context' is the ID type. */
584
585 SilcUInt32 silc_hash_id(void *key, void *user_context)
586 {
587   SilcIdType id_type = (SilcIdType)(SilcUInt32)user_context;
588   SilcUInt32 h = 0;
589   int i;
590
591   switch (id_type) {
592   case SILC_ID_CLIENT:
593     {
594       SilcClientID *id = (SilcClientID *)key;
595       SilcUInt32 g;
596   
597       /* The client ID is hashed by hashing the hash of the ID 
598          (which is a truncated MD5 hash of the nickname) so that we
599          can access the entry from the cache with both Client ID but
600          with just a hash from the ID as well. */
601
602       for (i = 0; i < sizeof(id->hash); i++) {
603         h = (h << 4) + id->hash[i];
604         if ((g = h & 0xf0000000)) {
605           h = h ^ (g >> 24);
606           h = h ^ g;
607         }
608       }
609
610       return h;
611     }
612     break;
613   case SILC_ID_SERVER:
614     {
615       SilcServerID *id = (SilcServerID *)key;
616       
617       h = id->port * id->rnd;
618       for (i = 0; i < id->ip.data_len; i++)
619         h ^= id->ip.data[i];
620       
621       return h;
622     }
623     break;
624   case SILC_ID_CHANNEL:
625     {
626       SilcChannelID *id = (SilcChannelID *)key;
627       
628       h = id->port * id->rnd;
629       for (i = 0; i < id->ip.data_len; i++)
630         h ^= id->ip.data[i];
631       
632       return h;
633     }
634     break;
635   default:
636     break;
637   }
638
639   return h;
640 }
641
642 /* Hash binary data. The `user_context' is the data length. */
643
644 SilcUInt32 silc_hash_data(void *key, void *user_context)
645 {
646   SilcUInt32 len = (SilcUInt32)user_context, h = 0;
647   unsigned char *data = (unsigned char *)key;
648   int i;
649
650   h = (data[0] * data[len - 1] + 1) * len;
651   for (i = 0; i < len; i++)
652     h ^= data[i];
653
654   return h;
655 }
656
657 /* Compares two strings. May be used as SilcHashTable comparison function. */
658
659 bool silc_hash_string_compare(void *key1, void *key2, void *user_context)
660 {
661   return !strcasecmp((char *)key1, (char *)key2);
662 }
663
664 /* Compares two ID's. May be used as SilcHashTable comparison function. 
665    The Client ID's compares only the hash of the Client ID not any other
666    part of the Client ID. Other ID's are fully compared. */
667
668 bool silc_hash_id_compare(void *key1, void *key2, void *user_context)
669 {
670   SilcIdType id_type = (SilcIdType)(SilcUInt32)user_context;
671   return (id_type == SILC_ID_CLIENT ? 
672           SILC_ID_COMPARE_HASH((SilcClientID *)key1, (SilcClientID *)key2) :
673           SILC_ID_COMPARE_TYPE(key1, key2, id_type));
674 }
675
676 /* Compare two Client ID's entirely and not just the hash from the ID. */
677
678 bool silc_hash_client_id_compare(void *key1, void *key2, void *user_context)
679 {
680   return SILC_ID_COMPARE_TYPE(key1, key2, SILC_ID_CLIENT);
681 }
682
683 /* Compares binary data. May be used as SilcHashTable comparison function. */
684
685 bool silc_hash_data_compare(void *key1, void *key2, void *user_context)
686 {
687   SilcUInt32 len = (SilcUInt32)user_context;
688   return !memcmp(key1, key2, len);
689 }
690
691 /* Parses mode mask and returns the mode as string. */
692
693 char *silc_client_chmode(SilcUInt32 mode, const char *cipher, const char *hmac)
694 {
695   char string[100];
696
697   if (!mode)
698     return NULL;
699
700   memset(string, 0, sizeof(string));
701
702   if (mode & SILC_CHANNEL_MODE_PRIVATE)
703     strncat(string, "p", 1);
704
705   if (mode & SILC_CHANNEL_MODE_SECRET)
706     strncat(string, "s", 1);
707
708   if (mode & SILC_CHANNEL_MODE_PRIVKEY)
709     strncat(string, "k", 1);
710
711   if (mode & SILC_CHANNEL_MODE_INVITE)
712     strncat(string, "i", 1);
713
714   if (mode & SILC_CHANNEL_MODE_TOPIC)
715     strncat(string, "t", 1);
716
717   if (mode & SILC_CHANNEL_MODE_ULIMIT)
718     strncat(string, "l", 1);
719
720   if (mode & SILC_CHANNEL_MODE_PASSPHRASE)
721     strncat(string, "a", 1);
722
723   if (mode & SILC_CHANNEL_MODE_FOUNDER_AUTH)
724     strncat(string, "f", 1);
725
726   if (mode & SILC_CHANNEL_MODE_CIPHER)
727     strncat(string, cipher, strlen(cipher));
728
729   if (mode & SILC_CHANNEL_MODE_HMAC)
730     strncat(string, hmac, strlen(hmac));
731
732   /* Rest of mode is ignored */
733
734   return strdup(string);
735 }
736
737 /* Parses channel user mode mask and returns te mode as string */
738
739 char *silc_client_chumode(SilcUInt32 mode)
740 {
741   char string[4];
742
743   if (!mode)
744     return NULL;
745
746   memset(string, 0, sizeof(string));
747
748   if (mode & SILC_CHANNEL_UMODE_CHANFO)
749     strncat(string, "f", 1);
750
751   if (mode & SILC_CHANNEL_UMODE_CHANOP)
752     strncat(string, "o", 1);
753
754   return strdup(string);
755 }
756
757 /* Parses channel user mode and returns it as special mode character. */
758
759 char *silc_client_chumode_char(SilcUInt32 mode)
760 {
761   char string[4];
762
763   if (!mode)
764     return NULL;
765
766   memset(string, 0, sizeof(string));
767
768   if (mode & SILC_CHANNEL_UMODE_CHANFO)
769     strncat(string, "*", 1);
770
771   if (mode & SILC_CHANNEL_UMODE_CHANOP)
772     strncat(string, "@", 1);
773
774   return strdup(string);
775 }
776
777 /* Creates fingerprint from data, usually used with SHA1 digests */
778
779 char *silc_fingerprint(const unsigned char *data, SilcUInt32 data_len)
780 {
781   char fingerprint[64], *cp;
782   int i;
783
784   memset(fingerprint, 0, sizeof(fingerprint));
785   cp = fingerprint;
786   for (i = 0; i < data_len; i++) {
787     snprintf(cp, sizeof(fingerprint), "%02X", data[i]);
788     cp += 2;
789     
790     if ((i + 1) % 2 == 0)
791       snprintf(cp++, sizeof(fingerprint), " ");
792
793     if ((i + 1) % 10 == 0)
794       snprintf(cp++, sizeof(fingerprint), " ");
795   }
796   i--;
797   if ((i + 1) % 2 == 0)
798     cp[-2] = 0;
799   if ((i + 1) % 10 == 0)
800     cp[-1] = 0;
801   
802   return strdup(fingerprint);
803 }