diff options
Diffstat (limited to 'src/cli/identity/gnunet-identity.c')
-rw-r--r-- | src/cli/identity/gnunet-identity.c | 624 |
1 files changed, 624 insertions, 0 deletions
diff --git a/src/cli/identity/gnunet-identity.c b/src/cli/identity/gnunet-identity.c new file mode 100644 index 000000000..9fe4ccc51 --- /dev/null +++ b/src/cli/identity/gnunet-identity.c | |||
@@ -0,0 +1,624 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | Copyright (C) 2013, 2018, 2019 GNUnet e.V. | ||
4 | |||
5 | GNUnet is free software: you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Affero General Public License as published | ||
7 | by the Free Software Foundation, either version 3 of the License, | ||
8 | or (at your option) any later version. | ||
9 | |||
10 | GNUnet is distributed in the hope that it will be useful, but | ||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | Affero General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Affero General Public License | ||
16 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | SPDX-License-Identifier: AGPL3.0-or-later | ||
19 | */ | ||
20 | /** | ||
21 | * @file identity/gnunet-identity.c | ||
22 | * @brief IDENTITY management command line tool | ||
23 | * @author Christian Grothoff | ||
24 | * | ||
25 | * Todo: | ||
26 | * - add options to get default egos | ||
27 | */ | ||
28 | #include "platform.h" | ||
29 | #include "gnunet_util_lib.h" | ||
30 | #include "gnunet_identity_service.h" | ||
31 | |||
32 | |||
33 | /** | ||
34 | * Return value from main on timeout. | ||
35 | */ | ||
36 | #define TIMEOUT_STATUS_CODE 40 | ||
37 | |||
38 | /** | ||
39 | * Handle to IDENTITY service. | ||
40 | */ | ||
41 | static struct GNUNET_IDENTITY_Handle *sh; | ||
42 | |||
43 | /** | ||
44 | * Was "list" specified? | ||
45 | */ | ||
46 | static int list; | ||
47 | |||
48 | /** | ||
49 | * Was "monitor" specified? | ||
50 | */ | ||
51 | static int monitor; | ||
52 | |||
53 | /** | ||
54 | * Was "private" specified? | ||
55 | */ | ||
56 | static int private_keys; | ||
57 | |||
58 | /** | ||
59 | * Was "verbose" specified? | ||
60 | */ | ||
61 | static unsigned int verbose; | ||
62 | |||
63 | /** | ||
64 | * Was "quiet" specified? | ||
65 | */ | ||
66 | static int quiet; | ||
67 | |||
68 | /** | ||
69 | * Was "eddsa" specified? | ||
70 | */ | ||
71 | static int type_eddsa; | ||
72 | |||
73 | /** | ||
74 | * -W option | ||
75 | */ | ||
76 | static char *write_msg; | ||
77 | |||
78 | /** | ||
79 | * -R option | ||
80 | */ | ||
81 | static char *read_msg; | ||
82 | |||
83 | /** | ||
84 | * -C option | ||
85 | */ | ||
86 | static char *create_ego; | ||
87 | |||
88 | /** | ||
89 | * -D option | ||
90 | */ | ||
91 | static char *delete_ego; | ||
92 | |||
93 | /** | ||
94 | * -P option | ||
95 | */ | ||
96 | static char *privkey_ego; | ||
97 | |||
98 | /** | ||
99 | * -k option | ||
100 | */ | ||
101 | static char *pubkey_msg; | ||
102 | |||
103 | /** | ||
104 | * -s option. | ||
105 | */ | ||
106 | static char *set_ego; | ||
107 | |||
108 | /** | ||
109 | * Operation handle for set operation. | ||
110 | */ | ||
111 | static struct GNUNET_IDENTITY_Operation *set_op; | ||
112 | |||
113 | /** | ||
114 | * Handle for create operation. | ||
115 | */ | ||
116 | static struct GNUNET_IDENTITY_Operation *create_op; | ||
117 | |||
118 | /** | ||
119 | * Handle for delete operation. | ||
120 | */ | ||
121 | static struct GNUNET_IDENTITY_Operation *delete_op; | ||
122 | |||
123 | /** | ||
124 | * Private key from command line option, or NULL. | ||
125 | */ | ||
126 | struct GNUNET_CRYPTO_PrivateKey pk; | ||
127 | |||
128 | /** | ||
129 | * Value to return from #main(). | ||
130 | */ | ||
131 | static int global_ret; | ||
132 | |||
133 | |||
134 | /** | ||
135 | * Task run on shutdown. | ||
136 | * | ||
137 | * @param cls NULL | ||
138 | */ | ||
139 | static void | ||
140 | shutdown_task (void *cls) | ||
141 | { | ||
142 | if (NULL != set_op) | ||
143 | { | ||
144 | GNUNET_IDENTITY_cancel (set_op); | ||
145 | set_op = NULL; | ||
146 | } | ||
147 | if (NULL != create_op) | ||
148 | { | ||
149 | GNUNET_IDENTITY_cancel (create_op); | ||
150 | create_op = NULL; | ||
151 | } | ||
152 | if (NULL != delete_op) | ||
153 | { | ||
154 | GNUNET_IDENTITY_cancel (delete_op); | ||
155 | delete_op = NULL; | ||
156 | } | ||
157 | if (NULL != set_ego) | ||
158 | { | ||
159 | GNUNET_free (set_ego); | ||
160 | set_ego = NULL; | ||
161 | } | ||
162 | GNUNET_IDENTITY_disconnect (sh); | ||
163 | sh = NULL; | ||
164 | } | ||
165 | |||
166 | |||
167 | /** | ||
168 | * Test if we are finished yet. | ||
169 | */ | ||
170 | static void | ||
171 | test_finished (void) | ||
172 | { | ||
173 | if ( (NULL == create_op) && | ||
174 | (NULL == delete_op) && | ||
175 | (NULL == set_op) && | ||
176 | (NULL == write_msg) && | ||
177 | (NULL == read_msg) && | ||
178 | (! list) && | ||
179 | (! monitor)) | ||
180 | { | ||
181 | if (TIMEOUT_STATUS_CODE == global_ret) | ||
182 | global_ret = 0; | ||
183 | GNUNET_SCHEDULER_shutdown (); | ||
184 | } | ||
185 | } | ||
186 | |||
187 | |||
188 | /** | ||
189 | * Deletion operation finished. | ||
190 | * | ||
191 | * @param cls pointer to operation handle | ||
192 | * @param ec the error code | ||
193 | */ | ||
194 | static void | ||
195 | delete_finished (void *cls, | ||
196 | enum GNUNET_ErrorCode ec) | ||
197 | { | ||
198 | struct GNUNET_IDENTITY_Operation **op = cls; | ||
199 | |||
200 | *op = NULL; | ||
201 | if (GNUNET_EC_NONE != ec) | ||
202 | fprintf (stderr, "%s\n", GNUNET_ErrorCode_get_hint (ec)); | ||
203 | test_finished (); | ||
204 | } | ||
205 | |||
206 | |||
207 | /** | ||
208 | * Creation operation finished. | ||
209 | * | ||
210 | * @param cls pointer to operation handle | ||
211 | * @param pk private key of the ego, or NULL on error | ||
212 | * @param ec the error code | ||
213 | */ | ||
214 | static void | ||
215 | create_finished (void *cls, | ||
216 | const struct GNUNET_CRYPTO_PrivateKey *pk, | ||
217 | enum GNUNET_ErrorCode ec) | ||
218 | { | ||
219 | struct GNUNET_IDENTITY_Operation **op = cls; | ||
220 | |||
221 | *op = NULL; | ||
222 | if (NULL == pk) | ||
223 | { | ||
224 | fprintf (stderr, | ||
225 | _ ("Failed to create ego: %s\n"), | ||
226 | GNUNET_ErrorCode_get_hint (ec)); | ||
227 | global_ret = 1; | ||
228 | } | ||
229 | else if (verbose) | ||
230 | { | ||
231 | struct GNUNET_CRYPTO_PublicKey pub; | ||
232 | char *pubs; | ||
233 | |||
234 | GNUNET_CRYPTO_key_get_public (pk, &pub); | ||
235 | pubs = GNUNET_CRYPTO_public_key_to_string (&pub); | ||
236 | if (private_keys) | ||
237 | { | ||
238 | char *privs; | ||
239 | |||
240 | privs = GNUNET_CRYPTO_private_key_to_string (pk); | ||
241 | fprintf (stdout, "%s - %s\n", pubs, privs); | ||
242 | GNUNET_free (privs); | ||
243 | } | ||
244 | else | ||
245 | { | ||
246 | fprintf (stdout, "%s\n", pubs); | ||
247 | } | ||
248 | GNUNET_free (pubs); | ||
249 | } | ||
250 | test_finished (); | ||
251 | } | ||
252 | |||
253 | |||
254 | /** | ||
255 | * Encrypt a message given with -W, encrypted using public key of | ||
256 | * an identity given with -k. | ||
257 | */ | ||
258 | static void | ||
259 | write_encrypted_message (void) | ||
260 | { | ||
261 | struct GNUNET_CRYPTO_PublicKey recipient; | ||
262 | size_t ct_len = strlen (write_msg) + 1 | ||
263 | + GNUNET_CRYPTO_ENCRYPT_OVERHEAD_BYTES; | ||
264 | unsigned char ct[ct_len]; | ||
265 | if (GNUNET_CRYPTO_public_key_from_string (pubkey_msg, &recipient) != | ||
266 | GNUNET_SYSERR) | ||
267 | { | ||
268 | size_t msg_len = strlen (write_msg) + 1; | ||
269 | if (GNUNET_OK == GNUNET_CRYPTO_encrypt (write_msg, | ||
270 | msg_len, | ||
271 | &recipient, | ||
272 | ct, ct_len)) | ||
273 | { | ||
274 | char *serialized_msg; | ||
275 | serialized_msg = GNUNET_STRINGS_data_to_string_alloc (ct, ct_len); | ||
276 | fprintf (stdout, | ||
277 | "%s\n", | ||
278 | serialized_msg); | ||
279 | GNUNET_free (serialized_msg); | ||
280 | } | ||
281 | else | ||
282 | { | ||
283 | fprintf (stderr, "Error during encryption.\n"); | ||
284 | global_ret = 1; | ||
285 | } | ||
286 | } | ||
287 | else | ||
288 | { | ||
289 | fprintf (stderr, "Invalid recipient public key.\n"); | ||
290 | global_ret = 1; | ||
291 | } | ||
292 | } | ||
293 | |||
294 | |||
295 | /** | ||
296 | * Decrypt a message given with -R, encrypted using public key of @c ego | ||
297 | * and ephemeral key given with -k. | ||
298 | * | ||
299 | * @param ego ego whose private key is used for decryption | ||
300 | */ | ||
301 | static void | ||
302 | read_encrypted_message (struct GNUNET_IDENTITY_Ego *ego) | ||
303 | { | ||
304 | char *deserialized_msg; | ||
305 | size_t msg_len; | ||
306 | if (GNUNET_OK == GNUNET_STRINGS_string_to_data_alloc (read_msg, strlen ( | ||
307 | read_msg), | ||
308 | (void **) & | ||
309 | deserialized_msg, | ||
310 | &msg_len)) | ||
311 | { | ||
312 | if (GNUNET_OK == GNUNET_CRYPTO_decrypt (deserialized_msg, | ||
313 | msg_len, | ||
314 | GNUNET_IDENTITY_ego_get_private_key ( | ||
315 | ego), | ||
316 | deserialized_msg, msg_len)) | ||
317 | { | ||
318 | deserialized_msg[msg_len - 1] = '\0'; | ||
319 | fprintf (stdout, | ||
320 | "%s\n", | ||
321 | deserialized_msg); | ||
322 | } | ||
323 | else | ||
324 | { | ||
325 | fprintf (stderr, "Failed to decrypt message.\n"); | ||
326 | global_ret = 1; | ||
327 | } | ||
328 | GNUNET_free (deserialized_msg); | ||
329 | } | ||
330 | else | ||
331 | { | ||
332 | fprintf (stderr, "Invalid message format.\n"); | ||
333 | global_ret = 1; | ||
334 | } | ||
335 | } | ||
336 | |||
337 | |||
338 | /** | ||
339 | * If listing is enabled, prints information about the egos. | ||
340 | * | ||
341 | * This function is initially called for all egos and then again | ||
342 | * whenever a ego's identifier changes or if it is deleted. At the | ||
343 | * end of the initial pass over all egos, the function is once called | ||
344 | * with 'NULL' for 'ego'. That does NOT mean that the callback won't | ||
345 | * be invoked in the future or that there was an error. | ||
346 | * | ||
347 | * When used with 'GNUNET_IDENTITY_create' or 'GNUNET_IDENTITY_get', this | ||
348 | * function is only called ONCE, and 'NULL' being passed in 'ego' does | ||
349 | * indicate an error (for example because name is taken or no default value is | ||
350 | * known). If 'ego' is non-NULL and if '*ctx' is set in those callbacks, the | ||
351 | * value WILL be passed to a subsequent call to the identity callback of | ||
352 | * 'GNUNET_IDENTITY_connect' (if that one was not NULL). | ||
353 | * | ||
354 | * When an identity is renamed, this function is called with the | ||
355 | * (known) ego but the NEW identifier. | ||
356 | * | ||
357 | * When an identity is deleted, this function is called with the | ||
358 | * (known) ego and "NULL" for the 'identifier'. In this case, | ||
359 | * the 'ego' is henceforth invalid (and the 'ctx' should also be | ||
360 | * cleaned up). | ||
361 | * | ||
362 | * @param cls closure | ||
363 | * @param ego ego handle | ||
364 | * @param ctx context for application to store data for this ego | ||
365 | * (during the lifetime of this process, initially NULL) | ||
366 | * @param identifier identifier assigned by the user for this ego, | ||
367 | * NULL if the user just deleted the ego and it | ||
368 | * must thus no longer be used | ||
369 | */ | ||
370 | static void | ||
371 | print_ego (void *cls, | ||
372 | struct GNUNET_IDENTITY_Ego *ego, | ||
373 | void **ctx, | ||
374 | const char *identifier) | ||
375 | { | ||
376 | struct GNUNET_CRYPTO_PublicKey pk; | ||
377 | char *s; | ||
378 | char *privs; | ||
379 | |||
380 | if ( (NULL == ego) && | ||
381 | (NULL != set_ego) && | ||
382 | (NULL != read_msg) ) | ||
383 | { | ||
384 | fprintf (stderr, | ||
385 | "Ego `%s' is not known, cannot decrypt message.\n", | ||
386 | set_ego); | ||
387 | GNUNET_free (read_msg); | ||
388 | read_msg = NULL; | ||
389 | GNUNET_free (set_ego); | ||
390 | set_ego = NULL; | ||
391 | } | ||
392 | if ((NULL == ego) && (! monitor)) | ||
393 | { | ||
394 | list = 0; | ||
395 | test_finished (); | ||
396 | return; | ||
397 | } | ||
398 | if (! (list | monitor) && (NULL == read_msg)) | ||
399 | return; | ||
400 | if ( (NULL == ego) || | ||
401 | (NULL == identifier) ) | ||
402 | return; | ||
403 | if ( (NULL != set_ego) && | ||
404 | (0 != strcmp (identifier, | ||
405 | set_ego)) ) | ||
406 | return; | ||
407 | GNUNET_IDENTITY_ego_get_public_key (ego, &pk); | ||
408 | s = GNUNET_CRYPTO_public_key_to_string (&pk); | ||
409 | privs = GNUNET_CRYPTO_private_key_to_string ( | ||
410 | GNUNET_IDENTITY_ego_get_private_key (ego)); | ||
411 | if ((NULL != read_msg) && (NULL != set_ego)) | ||
412 | { | ||
413 | // due to the check above, set_ego and the identifier are equal | ||
414 | read_encrypted_message (ego); | ||
415 | GNUNET_free (read_msg); | ||
416 | read_msg = NULL; | ||
417 | } | ||
418 | else if ((monitor) || (NULL != identifier)) | ||
419 | { | ||
420 | if (quiet) | ||
421 | { | ||
422 | if (private_keys) | ||
423 | fprintf (stdout, "%s - %s\n", s, privs); | ||
424 | else | ||
425 | fprintf (stdout, "%s\n", s); | ||
426 | } | ||
427 | else | ||
428 | { | ||
429 | if (private_keys) | ||
430 | fprintf (stdout, "%s - %s - %s - %s\n", | ||
431 | identifier, s, privs, | ||
432 | (ntohl (pk.type) == GNUNET_PUBLIC_KEY_TYPE_ECDSA) ? | ||
433 | "ECDSA" : "EdDSA"); | ||
434 | else | ||
435 | fprintf (stdout, "%s - %s - %s\n", | ||
436 | identifier, s, | ||
437 | (ntohl (pk.type) == GNUNET_PUBLIC_KEY_TYPE_ECDSA) ? | ||
438 | "ECDSA" : "EdDSA"); | ||
439 | |||
440 | } | ||
441 | } | ||
442 | GNUNET_free (privs); | ||
443 | GNUNET_free (s); | ||
444 | } | ||
445 | |||
446 | |||
447 | /** | ||
448 | * Main function that will be run by the scheduler. | ||
449 | * | ||
450 | * @param cls closure | ||
451 | * @param args remaining command-line arguments | ||
452 | * @param cfgfile name of the configuration file used (for saving, can be NULL!) | ||
453 | * @param cfg configuration | ||
454 | */ | ||
455 | static void | ||
456 | run (void *cls, | ||
457 | char *const *args, | ||
458 | const char *cfgfile, | ||
459 | const struct GNUNET_CONFIGURATION_Handle *cfg) | ||
460 | { | ||
461 | if ((NULL != read_msg) && (NULL == set_ego)) | ||
462 | { | ||
463 | fprintf (stderr, | ||
464 | "Option -R requires options -e to be specified as well.\n"); | ||
465 | return; | ||
466 | } | ||
467 | |||
468 | if ((NULL != write_msg) && (NULL == pubkey_msg)) | ||
469 | { | ||
470 | fprintf (stderr, "Option -W requires option -k to be specified as well.\n"); | ||
471 | return; | ||
472 | } | ||
473 | sh = GNUNET_IDENTITY_connect (cfg, | ||
474 | (monitor | list) || | ||
475 | (NULL != set_ego) | ||
476 | ? &print_ego | ||
477 | : NULL, | ||
478 | NULL); | ||
479 | if (NULL != write_msg) | ||
480 | { | ||
481 | write_encrypted_message (); | ||
482 | GNUNET_free (write_msg); | ||
483 | write_msg = NULL; | ||
484 | } | ||
485 | // read message is handled in ego callback (print_ego) | ||
486 | if (NULL != delete_ego) | ||
487 | delete_op = | ||
488 | GNUNET_IDENTITY_delete (sh, | ||
489 | delete_ego, | ||
490 | &delete_finished, | ||
491 | &delete_op); | ||
492 | if (NULL != create_ego) | ||
493 | { | ||
494 | if (NULL != privkey_ego) | ||
495 | { | ||
496 | GNUNET_STRINGS_string_to_data (privkey_ego, | ||
497 | strlen (privkey_ego), | ||
498 | &pk, | ||
499 | sizeof(struct | ||
500 | GNUNET_CRYPTO_PrivateKey)); | ||
501 | create_op = | ||
502 | GNUNET_IDENTITY_create (sh, | ||
503 | create_ego, | ||
504 | &pk, | ||
505 | 0, // Ignored | ||
506 | &create_finished, | ||
507 | &create_op); | ||
508 | } | ||
509 | else | ||
510 | create_op = | ||
511 | GNUNET_IDENTITY_create (sh, | ||
512 | create_ego, | ||
513 | NULL, | ||
514 | (type_eddsa) ? | ||
515 | GNUNET_PUBLIC_KEY_TYPE_EDDSA : | ||
516 | GNUNET_PUBLIC_KEY_TYPE_ECDSA, | ||
517 | &create_finished, | ||
518 | &create_op); | ||
519 | } | ||
520 | GNUNET_SCHEDULER_add_shutdown (&shutdown_task, | ||
521 | NULL); | ||
522 | test_finished (); | ||
523 | } | ||
524 | |||
525 | |||
526 | /** | ||
527 | * The main function. | ||
528 | * | ||
529 | * @param argc number of arguments from the command line | ||
530 | * @param argv command line arguments | ||
531 | * @return 0 ok, 1 on error | ||
532 | */ | ||
533 | int | ||
534 | main (int argc, char *const *argv) | ||
535 | { | ||
536 | struct GNUNET_GETOPT_CommandLineOption options[] = { | ||
537 | GNUNET_GETOPT_option_string ('C', | ||
538 | "create", | ||
539 | "NAME", | ||
540 | gettext_noop ("create ego NAME"), | ||
541 | &create_ego), | ||
542 | GNUNET_GETOPT_option_string ('D', | ||
543 | "delete", | ||
544 | "NAME", | ||
545 | gettext_noop ("delete ego NAME "), | ||
546 | &delete_ego), | ||
547 | GNUNET_GETOPT_option_string ('P', | ||
548 | "privkey", | ||
549 | "PRIVATE_KEY", | ||
550 | gettext_noop ( | ||
551 | "set the private key for the identity to PRIVATE_KEY (use together with -C)"), | ||
552 | &privkey_ego), | ||
553 | GNUNET_GETOPT_option_string ('R', | ||
554 | "read", | ||
555 | "MESSAGE", | ||
556 | gettext_noop ( | ||
557 | "Read and decrypt message encrypted for the given ego (use together with -e EGO)"), | ||
558 | &read_msg), | ||
559 | GNUNET_GETOPT_option_string ('W', | ||
560 | "write", | ||
561 | "MESSAGE", | ||
562 | gettext_noop ( | ||
563 | "Encrypt and write message for recipient identity PULBIC_KEY, (use together with -k RECIPIENT_PUBLIC_KEY)"), | ||
564 | &write_msg), | ||
565 | GNUNET_GETOPT_option_flag ('X', | ||
566 | "eddsa", | ||
567 | gettext_noop ( | ||
568 | "generate an EdDSA identity. (use together with -C) EXPERIMENTAL"), | ||
569 | &type_eddsa), | ||
570 | GNUNET_GETOPT_option_flag ('d', | ||
571 | "display", | ||
572 | gettext_noop ("display all egos"), | ||
573 | &list), | ||
574 | GNUNET_GETOPT_option_flag ('q', | ||
575 | "quiet", | ||
576 | gettext_noop ("reduce output"), | ||
577 | &quiet), | ||
578 | GNUNET_GETOPT_option_string ( | ||
579 | 'e', | ||
580 | "ego", | ||
581 | "NAME", | ||
582 | gettext_noop ( | ||
583 | "restrict results to NAME (use together with -d) or read and decrypt a message for NAME (use together with -R)"), | ||
584 | &set_ego), | ||
585 | GNUNET_GETOPT_option_string ('k', | ||
586 | "key", | ||
587 | "PUBLIC_KEY", | ||
588 | gettext_noop ( | ||
589 | "The public key of the recipient (with -W)"), | ||
590 | &pubkey_msg), | ||
591 | GNUNET_GETOPT_option_flag ('m', | ||
592 | "monitor", | ||
593 | gettext_noop ("run in monitor mode egos"), | ||
594 | &monitor), | ||
595 | GNUNET_GETOPT_option_flag ('p', | ||
596 | "private-keys", | ||
597 | gettext_noop ("display private keys as well"), | ||
598 | &private_keys), | ||
599 | GNUNET_GETOPT_option_verbose (&verbose), | ||
600 | GNUNET_GETOPT_OPTION_END | ||
601 | }; | ||
602 | int res; | ||
603 | |||
604 | if (GNUNET_OK != | ||
605 | GNUNET_STRINGS_get_utf8_args (argc, argv, | ||
606 | &argc, &argv)) | ||
607 | return 4; | ||
608 | global_ret = TIMEOUT_STATUS_CODE; /* timeout */ | ||
609 | res = GNUNET_PROGRAM_run (argc, | ||
610 | argv, | ||
611 | "gnunet-identity", | ||
612 | gettext_noop ("Maintain egos"), | ||
613 | options, | ||
614 | &run, | ||
615 | NULL); | ||
616 | GNUNET_free_nz ((void *) argv); | ||
617 | |||
618 | if (GNUNET_OK != res) | ||
619 | return 3; | ||
620 | return global_ret; | ||
621 | } | ||
622 | |||
623 | |||
624 | /* end of gnunet-identity.c */ | ||