1 /*
2
3 client.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 /* $Id: clientutil.c,v 1.19 2002/02/24 11:14:43 priikone Exp $ */
21
22 #include "clientincludes.h"
23
24 /* Routine used to print lines to window. This can split the
25 line neatly if a word would overlap the line. */
26
27 void silc_print_to_window(WINDOW *win, char *message)
28 {
29 int str_len, len;
30
31 str_len = strlen(message);
32
33 if (str_len > COLS - 1) {
34 /* Split overlapping words to next line */
35 /* XXX In principal this is wrong as this modifies the original
36 string as it replaces the last ' ' with '\n'. This could be done
37 with little more work so that it would not replace anything. */
38 len = COLS - 1;
39 while (1) {
40
41 while (len && message[len] != ' ')
42 len--;
43
44 if (!len)
45 break;
46
47 message[len] = '\n';
48 len += COLS - 1;
49 if (len > str_len)
50 break;
51 }
52 }
53
54 wprintw(win, "%s", message);
55 wrefresh(win);
56 }
57
58 /* Prints message to the screen. This is used to print the messages
59 user is typed and message that came on channels. */
60
61 void silc_print(SilcClient client, char *msg, ...)
62 {
63 va_list vp;
64 char message[2048];
65 SilcClientInternal app = client->application;
66
67 memset(message, 0, sizeof(message));
68 strncat(message, "\n ", 2);
69
70 va_start(vp, msg);
71 vsprintf(message + 1, msg, vp);
72 va_end(vp);
73
74 /* Print the message */
75 silc_print_to_window(app->screen->output_win[0], message);
76 }
77
78 /* Returns user's mail path */
79
80 char *silc_get_mail_path()
81 {
82 char pathbuf[MAXPATHLEN];
83 char *path;
84
85 #ifndef _PATH_MAILDIR
86 #define _PATH_MAILDIR "/var/mail"
87 #endif
88
89 path = getenv("MAIL");
90 if (path) {
91 strncpy(pathbuf, path, strlen(path));
92 } else {
93 strcpy(pathbuf, _PATH_MAILDIR);
94 strcat(pathbuf, "/");
95 strcat(pathbuf, silc_get_username());
96 }
97
98 return strdup(pathbuf);
99 }
100
101 /* gets the number of the user's mails, if possible */
102
103 int silc_get_number_of_emails()
104 {
105 FILE *tl;
106 int num = 0;
107 char *filename;
108 char data[1024];
109
110 filename = silc_get_mail_path();
111
112 tl = fopen(filename, "r");
113 if (!tl) {
114 fprintf(stderr, "Couldn't open mail file (%s).\n", filename);
115 } else {
116 while((fscanf(tl, "%s", data)) != EOF) {
117 if(!strcmp(data, "From:"))
118 num++;
119 }
120
121 fclose(tl);
122 }
123
124 return num;
125 }
126
127 /* Returns time til next minute changes. Used to update the clock when
128 needed. */
129
130 int silc_client_time_til_next_min()
131 {
132 time_t curtime;
133 struct tm *min;
134
135 curtime = time(0);
136 min = localtime(&curtime);
137
138 return 60 - min->tm_sec;
139 }
140
141 /* Asks yes/no from user on the input line. Returns TRUE on "yes" and
142 FALSE on "no". */
143
144 int silc_client_ask_yes_no(SilcClient client, char *prompt)
145 {
146 SilcClientInternal app = (SilcClientInternal)client->application;
147 char answer[4];
148 int ret;
149
150 again:
151 silc_screen_input_reset(app->screen);
152
153 /* Print prompt */
154 wattroff(app->screen->input_win, A_INVIS);
155 silc_screen_input_print_prompt(app->screen, prompt);
156
157 /* Get string */
158 memset(answer, 0, sizeof(answer));
159 echo();
160 wgetnstr(app->screen->input_win, answer, sizeof(answer));
161 if (!strncasecmp(answer, "yes", strlen(answer)) ||
162 !strncasecmp(answer, "y", strlen(answer))) {
163 ret = TRUE;
164 } else if (!strncasecmp(answer, "no", strlen(answer)) ||
165 !strncasecmp(answer, "n", strlen(answer))) {
166 ret = FALSE;
167 } else {
168 silc_say(client, app->conn, "Type yes or no");
169 goto again;
170 }
171 noecho();
172
173 silc_screen_input_reset(app->screen);
174
175 return ret;
176 }
177
178 /* Lists supported (builtin) ciphers */
179
180 void silc_client_list_ciphers()
181 {
182 char *ciphers = silc_cipher_get_supported();
183 fprintf(stdout, "%s\n", ciphers);
184 silc_free(ciphers);
185 }
186
187 /* Lists supported (builtin) hash functions */
188
189 void silc_client_list_hash_funcs()
190 {
191 char *hash = silc_hash_get_supported();
192 fprintf(stdout, "%s\n", hash);
193 silc_free(hash);
194 }
195
196 /* Lists supported PKCS algorithms */
197
198 void silc_client_list_pkcs()
199 {
200 char *pkcs = silc_pkcs_get_supported();
201 fprintf(stdout, "%s\n", pkcs);
202 silc_free(pkcs);
203 }
204
205 /* Displays input prompt on command line and takes input data from user */
206
207 char *silc_client_get_input(const char *prompt)
208 {
209 char input[2048];
210 int fd;
211
212 fd = open("/dev/tty", O_RDONLY);
213 if (fd < 0) {
214 fprintf(stderr, "silc: %s\n", strerror(errno));
215 return NULL;
216 }
217
218 memset(input, 0, sizeof(input));
219
220 printf("%s", prompt);
221 fflush(stdout);
222
223 if ((read(fd, input, sizeof(input))) < 0) {
224 fprintf(stderr, "silc: %s\n", strerror(errno));
225 return NULL;
226 }
227
228 if (strlen(input) <= 1)
229 return NULL;
230
231 if (strchr(input, '\n'))
232 *strchr(input, '\n') = '\0';
233
234 return strdup(input);
235 }
236
237 /* Displays prompt on command line and takes passphrase with echo
238 off from user. */
239
240 char *silc_client_get_passphrase(const char *prompt)
241 {
242 #if 0
243 char input[2048];
244 char *ret;
245 int fd;
246 struct termios to;
247 struct termios to_old;
248
249 fd = open("/dev/tty", O_RDONLY);
250 if (fd < 0) {
251 fprintf(stderr, "silc: %s\n", strerror(errno));
252 return NULL;
253 }
254
255 signal(SIGINT, SIG_IGN);
256
257 /* Get terminal info */
258 tcgetattr(fd, &to);
259 to_old = to;
260
261 /* Echo OFF */
262 to.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
263 tcsetattr(fd, TCSANOW, &to);
264
265 memset(input, 0, sizeof(input));
266
267 printf("%s", prompt);
268 fflush(stdout);
269
270 if ((read(fd, input, sizeof(input))) < 0) {
271 fprintf(stderr, "silc: %s\n", strerror(errno));
272 return NULL;
273 }
274
275 if (strlen(input) <= 1) {
276 tcsetattr(fd, TCSANOW, &to_old);
277 return NULL;
278 }
279
280 if (strchr(input, '\n'))
281 *strchr(input, '\n') = '\0';
282
283 /* Restore old terminfo */
284 tcsetattr(fd, TCSANOW, &to_old);
285 signal(SIGINT, SIG_DFL);
286
287 ret = silc_calloc(strlen(input), sizeof(char));
288 memcpy(ret, input, strlen(input));
289 memset(input, 0, sizeof(input));
290 return ret;
291 #else
292 return NULL;
293 #endif
294 }
295
296 /* Returns identifier string for public key generation. */
297
298 char *silc_client_create_identifier()
299 {
300 char *username = NULL, *realname = NULL;
301 char hostname[256], email[256];
302
303 /* Get realname */
304 realname = silc_get_real_name();
305
306 /* Get hostname */
307 memset(hostname, 0, sizeof(hostname));
308 gethostname(hostname, sizeof(hostname));
309
310 /* Get username (mandatory) */
311 username = silc_get_username();
312 if (!username)
313 return NULL;
314
315 /* Create default email address, whether it is right or not */
316 snprintf(email, sizeof(email), "%s@%s", username, hostname);
317
318 return silc_pkcs_encode_identifier(username, hostname, realname, email,
319 NULL, NULL);
320 }
321
322 /* Creates new public key and private key pair. This is used only
323 when user wants to create new key pair from command line. */
324
325 int silc_client_create_key_pair(char *pkcs_name, int bits,
326 char *public_key, char *private_key,
327 char *identifier,
328 SilcPublicKey *ret_pub_key,
329 SilcPrivateKey *ret_prv_key)
330 {
331 SilcPKCS pkcs;
332 SilcPublicKey pub_key;
333 SilcPrivateKey prv_key;
334 SilcRng rng;
335 unsigned char *key;
336 SilcUInt32 key_len;
337 char line[256];
338 char *pkfile = NULL, *prvfile = NULL;
339
340 if (!pkcs_name || !public_key || !private_key)
341 printf("\
342 New pair of keys will be created. Please, answer to following questions.\n\
343 ");
344
345 if (!pkcs_name) {
346 again_name:
347 pkcs_name =
348 silc_client_get_input("PKCS name (l to list names) [rsa]: ");
349 if (!pkcs_name)
350 pkcs_name = strdup("rsa");
351
352 if (*pkcs_name == 'l' || *pkcs_name == 'L') {
353 silc_client_list_pkcs();
354 silc_free(pkcs_name);
355 goto again_name;
356 }
357 }
358
359 if (!silc_pkcs_is_supported(pkcs_name)) {
360 fprintf(stderr, "Unknown PKCS `%s'", pkcs_name);
361 return FALSE;
362 }
363
364 if (!bits) {
365 char *length = NULL;
366 length =
367 silc_client_get_input("Key length in bits [1024]: ");
368 if (!length)
369 bits = 1024;
370 else
371 bits = atoi(length);
372 }
373
374 if (!identifier) {
375 char *def = silc_client_create_identifier();
376
377 memset(line, 0, sizeof(line));
378 if (def)
379 snprintf(line, sizeof(line), "Identifier [%s]: ", def);
380 else
381 snprintf(line, sizeof(line),
382 "Identifier (eg. UN=jon, HN=jon.dummy.com, "
383 "RN=Jon Johnson, E=jon@dummy.com): ");
384
385 while (!identifier) {
386 identifier = silc_client_get_input(line);
387 if (!identifier && def)
388 identifier = strdup(def);
389 }
390
391 if (def)
392 silc_free(def);
393 }
394
395 rng = silc_rng_alloc();
396 silc_rng_init(rng);
397 silc_rng_global_init(rng);
398
399 if (!public_key) {
400 memset(line, 0, sizeof(line));
401 snprintf(line, sizeof(line), "Public key filename [%s] ",
402 SILC_CLIENT_PUBLIC_KEY_NAME);
403 pkfile = silc_client_get_input(line);
404 if (!pkfile)
405 pkfile = SILC_CLIENT_PUBLIC_KEY_NAME;
406 } else {
407 pkfile = public_key;
408 }
409
410 if (!private_key) {
411 memset(line, 0, sizeof(line));
412 snprintf(line, sizeof(line), "Public key filename [%s] ",
413 SILC_CLIENT_PRIVATE_KEY_NAME);
414 prvfile = silc_client_get_input(line);
415 if (!prvfile)
416 prvfile = SILC_CLIENT_PRIVATE_KEY_NAME;
417 } else {
418 prvfile = private_key;
419 }
420
421 /* Generate keys */
422 silc_pkcs_alloc(pkcs_name, &pkcs);
423 pkcs->pkcs->init(pkcs->context, bits, rng);
424
425 /* Save public key into file */
426 key = silc_pkcs_get_public_key(pkcs, &key_len);
427 pub_key = silc_pkcs_public_key_alloc(pkcs->pkcs->name, identifier,
428 key, key_len);
429 silc_pkcs_save_public_key(pkfile, pub_key, SILC_PKCS_FILE_PEM);
430 if (ret_pub_key)
431 *ret_pub_key = pub_key;
432
433 memset(key, 0, sizeof(key_len));
434 silc_free(key);
435
436 /* Save private key into file */
437 key = silc_pkcs_get_private_key(pkcs, &key_len);
438 prv_key = silc_pkcs_private_key_alloc(pkcs->pkcs->name, key, key_len);
439
440 silc_pkcs_save_private_key(prvfile, prv_key, NULL, SILC_PKCS_FILE_BIN);
441 if (ret_prv_key)
442 *ret_prv_key = prv_key;
443
444 printf("Public key has been saved into `%s'.\n", pkfile);
445 printf("Private key has been saved into `%s'.\n", prvfile);
446 printf("Press <Enter> to continue...\n");
447 getchar();
448
449 memset(key, 0, sizeof(key_len));
450 silc_free(key);
451
452 silc_rng_free(rng);
453 silc_pkcs_free(pkcs);
454
455 return TRUE;
456 }
457
458 /* This checks stats for various SILC files and directories. First it
459 checks if ~/.silc directory exist and is owned by the correct user. If
460 it doesn't exist, it will create the directory. After that it checks if
461 user's Public and Private key files exists and that they aren't expired.
462 If they doesn't exist or they are expired, they will be (re)created
463 after return. */
464
465 int silc_client_check_silc_dir()
466 {
467 char filename[256], file_public_key[256], file_private_key[256];
468 char servfilename[256], clientfilename[256];
469 char *identifier;
470 struct stat st;
471 struct passwd *pw;
472 int firstime = FALSE;
473 time_t curtime, modtime;
474
475 SILC_LOG_DEBUG(("Checking ~./silc directory"));
476
477 memset(filename, 0, sizeof(filename));
478 memset(file_public_key, 0, sizeof(file_public_key));
479 memset(file_private_key, 0, sizeof(file_private_key));
480
481 pw = getpwuid(getuid());
482 if (!pw) {
483 fprintf(stderr, "silc: %s\n", strerror(errno));
484 return FALSE;
485 }
486
487 identifier = silc_client_create_identifier();
488
489 /* We'll take home path from /etc/passwd file to be sure. */
490 snprintf(filename, sizeof(filename) - 1, "%s/.silc/", pw->pw_dir);
491 snprintf(servfilename, sizeof(servfilename) - 1, "%s/.silc/serverkeys",
492 pw->pw_dir);
493 snprintf(clientfilename, sizeof(clientfilename) - 1, "%s/.silc/clientkeys",
494 pw->pw_dir);
495
496 /*
497 * Check ~/.silc directory
498 */
499 if ((stat(filename, &st)) == -1) {
500 /* If dir doesn't exist */
501 if (errno == ENOENT) {
502 if (pw->pw_uid == geteuid()) {
503 if ((mkdir(filename, 0755)) == -1) {
504 fprintf(stderr, "Couldn't create `%s' directory\n", filename);
505 return FALSE;
506 }
507
508 /* Directory was created. First time running SILC */
509 firstime = TRUE;
510 } else {
511 fprintf(stderr, "Couldn't create `%s' directory due to a wrong uid!\n",
512 filename);
513 return FALSE;
514 }
515 } else {
516 fprintf(stderr, "%s\n", strerror(errno));
517 return FALSE;
518 }
519 } else {
520
521 /* Check the owner of the dir */
522 if (st.st_uid != 0 && st.st_uid != pw->pw_uid) {
523 fprintf(stderr, "You don't seem to own `%s' directory\n",
524 filename);
525 return FALSE;
526 }
527
528 /* Check the permissions of the dir */
529 if ((st.st_mode & 0777) != 0755) {
530 if ((chmod(filename, 0755)) == -1) {
531 fprintf(stderr, "Permissions for `%s' directory must be 0755\n",
532 filename);
533 return FALSE;
534 }
535 }
536 }
537
538 /*
539 * Check ~./silc/serverkeys directory
540 */
541 if ((stat(servfilename, &st)) == -1) {
542 /* If dir doesn't exist */
543 if (errno == ENOENT) {
544 if (pw->pw_uid == geteuid()) {
545 if ((mkdir(servfilename, 0755)) == -1) {
546 fprintf(stderr, "Couldn't create `%s' directory\n", servfilename);
547 return FALSE;
548 }
549 } else {
550 fprintf(stderr, "Couldn't create `%s' directory due to a wrong uid!\n",
551 servfilename);
552 return FALSE;
553 }
554 } else {
555 fprintf(stderr, "%s\n", strerror(errno));
556 return FALSE;
557 }
558 }
559
560 /*
561 * Check ~./silc/clientkeys directory
562 */
563 if ((stat(clientfilename, &st)) == -1) {
564 /* If dir doesn't exist */
565 if (errno == ENOENT) {
566 if (pw->pw_uid == geteuid()) {
567 if ((mkdir(clientfilename, 0755)) == -1) {
568 fprintf(stderr, "Couldn't create `%s' directory\n", clientfilename);
569 return FALSE;
570 }
571 } else {
572 fprintf(stderr, "Couldn't create `%s' directory due to a wrong uid!\n",
573 clientfilename);
574 return FALSE;
575 }
576 } else {
577 fprintf(stderr, "%s\n", strerror(errno));
578 return FALSE;
579 }
580 }
581
582 /*
583 * Check Public and Private keys
584 */
585 snprintf(file_public_key, sizeof(file_public_key) - 1, "%s%s",
586 filename, SILC_CLIENT_PUBLIC_KEY_NAME);
587 snprintf(file_private_key, sizeof(file_private_key) - 1, "%s%s",
588 filename, SILC_CLIENT_PRIVATE_KEY_NAME);
589
590 /* If running SILC first time */
591 if (firstime) {
592 fprintf(stdout, "Running SILC for the first time\n");
593 silc_client_create_key_pair(SILC_CLIENT_DEF_PKCS,
594 SILC_CLIENT_DEF_PKCS_LEN,
595 file_public_key, file_private_key,
596 identifier, NULL, NULL);
597 return TRUE;
598 }
599
600 if ((stat(file_public_key, &st)) == -1) {
601 /* If file doesn't exist */
602 if (errno == ENOENT) {
603 fprintf(stdout, "Your public key doesn't exist\n");
604 silc_client_create_key_pair(SILC_CLIENT_DEF_PKCS,
605 SILC_CLIENT_DEF_PKCS_LEN,
606 file_public_key,
607 file_private_key, identifier, NULL, NULL);
608 } else {
609 fprintf(stderr, "%s\n", strerror(errno));
610 return FALSE;
611 }
612 }
613
614 if ((stat(file_private_key, &st)) == -1) {
615 /* If file doesn't exist */
616 if (errno == ENOENT) {
617 fprintf(stdout, "Your private key doesn't exist\n");
618 silc_client_create_key_pair(SILC_CLIENT_DEF_PKCS,
619 SILC_CLIENT_DEF_PKCS_LEN,
620 file_public_key,
621 file_private_key, identifier, NULL, NULL);
622 } else {
623 fprintf(stderr, "%s\n", strerror(errno));
624 return FALSE;
625 }
626 }
627
628 /* Check the owner of the public key */
629 if (st.st_uid != 0 && st.st_uid != pw->pw_uid) {
630 fprintf(stderr, "You don't seem to own your public key!?\n");
631 return FALSE;
632 }
633
634 /* Check the owner of the private key */
635 if (st.st_uid != 0 && st.st_uid != pw->pw_uid) {
636 fprintf(stderr, "You don't seem to own your private key!?\n");
637 return FALSE;
638 }
639
640 /* Check the permissions for the private key */
641 if ((st.st_mode & 0777) != 0600) {
642 fprintf(stderr, "Wrong permissions in your private key file `%s'!\n"
643 "Trying to change them ... ", file_private_key);
644 if ((chmod(file_private_key, 0600)) == -1) {
645 fprintf(stderr,
646 "Failed to change permissions for private key file!\n"
647 "Permissions for your private key file must be 0600.\n");
648 return FALSE;
649 }
650 fprintf(stderr, "Done.\n\n");
651 }
652
653 /* See if the key has expired. */
654 modtime = st.st_mtime; /* last modified */
655 curtime = time(0) - modtime;
656
657 /* 86400 is seconds in a day. */
658 if (curtime >= (86400 * SILC_CLIENT_KEY_EXPIRES)) {
659 fprintf(stdout,
660 "--------------------------------------------------\n"
661 "Your private key has expired and needs to be\n"
662 "recreated. This will be done automatically now.\n"
663 "Your new key will expire in %d days from today.\n"
664 "--------------------------------------------------\n",
665 SILC_CLIENT_KEY_EXPIRES);
666
667 silc_client_create_key_pair(SILC_CLIENT_DEF_PKCS,
668 SILC_CLIENT_DEF_PKCS_LEN,
669 file_public_key,
670 file_private_key, identifier, NULL, NULL);
671 }
672
673 if (identifier)
674 silc_free(identifier);
675
676 return TRUE;
677 }
678
679 /* Loads public and private key from files. */
680
681 int silc_client_load_keys(SilcClient client)
682 {
683 char filename[256];
684 struct passwd *pw;
685
686 SILC_LOG_DEBUG(("Loading public and private keys"));
687
688 pw = getpwuid(getuid());
689 if (!pw)
690 return FALSE;
691
692 memset(filename, 0, sizeof(filename));
693 snprintf(filename, sizeof(filename) - 1, "%s/.silc/%s",
694 pw->pw_dir, SILC_CLIENT_PRIVATE_KEY_NAME);
695
696 if (silc_pkcs_load_private_key(filename, &client->private_key,
697 SILC_PKCS_FILE_BIN) == FALSE)
698 if (silc_pkcs_load_private_key(filename, &client->private_key,
699 SILC_PKCS_FILE_PEM) == FALSE)
700 return FALSE;
701
702 memset(filename, 0, sizeof(filename));
703 snprintf(filename, sizeof(filename) - 1, "%s/.silc/%s",
704 pw->pw_dir, SILC_CLIENT_PUBLIC_KEY_NAME);
705
706 if (silc_pkcs_load_public_key(filename, &client->public_key,
707 SILC_PKCS_FILE_PEM) == FALSE)
708 if (silc_pkcs_load_public_key(filename, &client->public_key,
709 SILC_PKCS_FILE_BIN) == FALSE)
710 return FALSE;
711
712 return TRUE;
713 }
714
715 /* Dumps the public key on screen. Used from the command line option. */
716
717 int silc_client_show_key(char *keyfile)
718 {
719 SilcPublicKey public_key;
720 SilcPublicKeyIdentifier ident;
721 char *fingerprint;
722 unsigned char *pk;
723 SilcUInt32 pk_len;
724 SilcPKCS pkcs;
725 int key_len = 0;
726
727 if (silc_pkcs_load_public_key(keyfile, &public_key,
728 SILC_PKCS_FILE_PEM) == FALSE)
729 if (silc_pkcs_load_public_key(keyfile, &public_key,
730 SILC_PKCS_FILE_BIN) == FALSE) {
731 fprintf(stderr, "Could not load public key file `%s'\n", keyfile);
732 return FALSE;
733 }
734
735 ident = silc_pkcs_decode_identifier(public_key->identifier);
736
737 pk = silc_pkcs_public_key_encode(public_key, &pk_len);
738 fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
739
740 if (silc_pkcs_alloc(public_key->name, &pkcs)) {
741 key_len = silc_pkcs_public_key_set(pkcs, public_key);
742 silc_pkcs_free(pkcs);
743 }
744
745 printf("Public key file : %s\n", keyfile);
746 printf("Algorithm : %s\n", public_key->name);
747 if (key_len)
748 printf("Key length (bits) : %d\n", key_len);
749 if (ident->realname)
750 printf("Real name : %s\n", ident->realname);
751 if (ident->username)
752 printf("Username : %s\n", ident->username);
753 if (ident->host)
754 printf("Hostname : %s\n", ident->host);
755 if (ident->email)
756 printf("Email : %s\n", ident->email);
757 if (ident->org)
758 printf("Organization : %s\n", ident->org);
759 if (ident->country)
760 printf("Country : %s\n", ident->country);
761 printf("Fingerprint (SHA1) : %s\n", fingerprint);
762
763 fflush(stdout);
764
765 silc_free(fingerprint);
766 silc_free(pk);
767 silc_pkcs_public_key_free(public_key);
768 silc_pkcs_free_identifier(ident);
769
770 return TRUE;
771 }
772
This page was automatically generated by the LXR engine.
Free-text search provided by Glimpse