diff options
Diffstat (limited to 'src/util/pseudonym.c')
-rw-r--r-- | src/util/pseudonym.c | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/src/util/pseudonym.c b/src/util/pseudonym.c new file mode 100644 index 000000000..6d9146613 --- /dev/null +++ b/src/util/pseudonym.c | |||
@@ -0,0 +1,545 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet | ||
3 | (C) 2003, 2004, 2005, 2006, 2007, 2008 Christian Grothoff (and other contributing authors) | ||
4 | |||
5 | GNUnet is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published | ||
7 | by the Free Software Foundation; either version 2, or (at your | ||
8 | 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 | General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License | ||
16 | along with GNUnet; see the file COPYING. If not, write to the | ||
17 | Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
18 | Boston, MA 02111-1307, USA. | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file util/pseudonym.c | ||
23 | * @brief helper functions | ||
24 | * @author Christian Grothoff | ||
25 | */ | ||
26 | |||
27 | #include "platform.h" | ||
28 | #include "gnunet_common.h" | ||
29 | #include "gnunet_container_lib.h" | ||
30 | #include "gnunet_disk_lib.h" | ||
31 | #include "gnunet_pseudonym_lib.h" | ||
32 | |||
33 | #define PS_METADATA_DIR DIR_SEPARATOR_STR "data" DIR_SEPARATOR_STR "pseudonyms/metadata" DIR_SEPARATOR_STR | ||
34 | #define PS_NAMES_DIR DIR_SEPARATOR_STR "data" DIR_SEPARATOR_STR "pseudonyms/names" DIR_SEPARATOR_STR | ||
35 | |||
36 | struct DiscoveryCallback | ||
37 | { | ||
38 | struct DiscoveryCallback *next; | ||
39 | GNUNET_PSEUDONYM_Iterator callback; | ||
40 | void *closure; | ||
41 | }; | ||
42 | |||
43 | static struct DiscoveryCallback *head; | ||
44 | |||
45 | /** | ||
46 | * Internal notification about new tracked URI. | ||
47 | */ | ||
48 | static void | ||
49 | internal_notify (const GNUNET_HashCode * id, | ||
50 | const struct GNUNET_CONTAINER_MetaData *md, int rating) | ||
51 | { | ||
52 | struct DiscoveryCallback *pos; | ||
53 | |||
54 | pos = head; | ||
55 | while (pos != NULL) | ||
56 | { | ||
57 | pos->callback (pos->closure, id, md, rating); | ||
58 | pos = pos->next; | ||
59 | } | ||
60 | } | ||
61 | |||
62 | /** | ||
63 | * Register callback to be invoked whenever we discover | ||
64 | * a new pseudonym. | ||
65 | */ | ||
66 | int | ||
67 | GNUNET_PSEUDONYM_discovery_callback_register (struct | ||
68 | GNUNET_CONFIGURATION_Handle | ||
69 | *cfg, | ||
70 | GNUNET_PSEUDONYM_Iterator | ||
71 | iterator, void *closure) | ||
72 | { | ||
73 | struct DiscoveryCallback *list; | ||
74 | |||
75 | list = GNUNET_malloc (sizeof (struct DiscoveryCallback)); | ||
76 | list->callback = iterator; | ||
77 | list->closure = closure; | ||
78 | list->next = head; | ||
79 | head = list; | ||
80 | GNUNET_PSEUDONYM_list_all (cfg, iterator, closure); | ||
81 | return GNUNET_OK; | ||
82 | } | ||
83 | |||
84 | /** | ||
85 | * Unregister pseudonym discovery callback. | ||
86 | */ | ||
87 | int | ||
88 | GNUNET_PSEUDONYM_discovery_callback_unregister (GNUNET_PSEUDONYM_Iterator | ||
89 | iterator, void *closure) | ||
90 | { | ||
91 | struct DiscoveryCallback *prev; | ||
92 | struct DiscoveryCallback *pos; | ||
93 | |||
94 | prev = NULL; | ||
95 | pos = head; | ||
96 | while ((pos != NULL) && | ||
97 | ((pos->callback != iterator) || (pos->closure != closure))) | ||
98 | { | ||
99 | prev = pos; | ||
100 | pos = pos->next; | ||
101 | } | ||
102 | if (pos == NULL) | ||
103 | return GNUNET_SYSERR; | ||
104 | if (prev == NULL) | ||
105 | head = pos->next; | ||
106 | else | ||
107 | prev->next = pos->next; | ||
108 | GNUNET_free (pos); | ||
109 | return GNUNET_OK; | ||
110 | } | ||
111 | |||
112 | |||
113 | /** | ||
114 | * Get the filename (or directory name) for the given | ||
115 | * pseudonym identifier and directory prefix. | ||
116 | */ | ||
117 | static char * | ||
118 | get_data_filename (struct GNUNET_CONFIGURATION_Handle | ||
119 | *cfg, const char *prefix, const GNUNET_HashCode * psid) | ||
120 | { | ||
121 | struct GNUNET_CRYPTO_HashAsciiEncoded enc; | ||
122 | |||
123 | if (psid != NULL) | ||
124 | GNUNET_CRYPTO_hash_to_enc (psid, &enc); | ||
125 | return GNUNET_DISK_get_home_filename (cfg, | ||
126 | GNUNET_CLIENT_SERVICE_NAME, | ||
127 | prefix, | ||
128 | (psid == | ||
129 | NULL) ? NULL : (const char *) &enc, | ||
130 | NULL); | ||
131 | } | ||
132 | |||
133 | static void | ||
134 | write_pseudonym_info (struct GNUNET_CONFIGURATION_Handle *cfg, | ||
135 | const GNUNET_HashCode * nsid, | ||
136 | const struct GNUNET_CONTAINER_MetaData *meta, | ||
137 | int32_t ranking, const char *ns_name) | ||
138 | { | ||
139 | unsigned int size; | ||
140 | unsigned int tag; | ||
141 | unsigned int off; | ||
142 | char *buf; | ||
143 | char *fn; | ||
144 | |||
145 | fn = get_data_filename (cfg, PS_METADATA_DIR, nsid); | ||
146 | GNUNET_assert (fn != NULL); | ||
147 | size = GNUNET_CONTAINER_meta_data_get_serialized_size (meta, | ||
148 | GNUNET_CONTAINER_META_DATA_SERIALIZE_FULL); | ||
149 | tag = size + sizeof (int) + 1; | ||
150 | off = 0; | ||
151 | if (ns_name != NULL) | ||
152 | { | ||
153 | off = strlen (ns_name); | ||
154 | tag += off; | ||
155 | } | ||
156 | buf = GNUNET_malloc (tag); | ||
157 | ((int *) buf)[0] = htonl (ranking); /* ranking */ | ||
158 | if (ns_name != NULL) | ||
159 | { | ||
160 | memcpy (&buf[sizeof (int)], ns_name, off + 1); | ||
161 | } | ||
162 | else | ||
163 | { | ||
164 | buf[sizeof (int)] = '\0'; | ||
165 | } | ||
166 | GNUNET_assert | ||
167 | (size == GNUNET_CONTAINER_meta_data_serialize (meta, | ||
168 | &buf[sizeof | ||
169 | (int) + | ||
170 | off + 1], | ||
171 | size, | ||
172 | GNUNET_CONTAINER_META_DATA_SERIALIZE_FULL)); | ||
173 | GNUNET_DISK_file_write (fn, buf, tag, "660"); | ||
174 | GNUNET_free (fn); | ||
175 | GNUNET_free (buf); | ||
176 | /* create entry for pseudonym name in names */ | ||
177 | GNUNET_free_non_null (GNUNET_PSEUDONYM_id_to_name (cfg, nsid)); | ||
178 | } | ||
179 | |||
180 | static int | ||
181 | read_info (struct GNUNET_CONFIGURATION_Handle *cfg, | ||
182 | const GNUNET_HashCode * nsid, | ||
183 | struct GNUNET_CONTAINER_MetaData **meta, | ||
184 | int32_t * ranking, char **ns_name) | ||
185 | { | ||
186 | unsigned long long len; | ||
187 | unsigned int size; | ||
188 | unsigned int zend; | ||
189 | struct stat sbuf; | ||
190 | char *buf; | ||
191 | char *fn; | ||
192 | |||
193 | if (meta != NULL) | ||
194 | *meta = NULL; | ||
195 | if (ns_name != NULL) | ||
196 | *ns_name = NULL; | ||
197 | fn = get_data_filename (cfg, PS_METADATA_DIR, nsid); | ||
198 | GNUNET_assert (fn != NULL); | ||
199 | |||
200 | if ((0 != STAT (fn, &sbuf)) | ||
201 | || (GNUNET_OK != GNUNET_DISK_file_size (fn, &len, GNUNET_YES))) | ||
202 | { | ||
203 | GNUNET_free (fn); | ||
204 | return GNUNET_SYSERR; | ||
205 | } | ||
206 | if (len <= sizeof (int) + 1) | ||
207 | { | ||
208 | GNUNET_free (fn); | ||
209 | return GNUNET_SYSERR; | ||
210 | } | ||
211 | if (len > 16 * 1024 * 1024) | ||
212 | { | ||
213 | /* too big, must be invalid! remove! */ | ||
214 | GNUNET_break (0); | ||
215 | if (0 != UNLINK (fn)) | ||
216 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", fn); | ||
217 | GNUNET_free (fn); | ||
218 | return GNUNET_SYSERR; | ||
219 | } | ||
220 | buf = GNUNET_malloc (len); | ||
221 | if (len != GNUNET_DISK_file_read (fn, len, buf)) | ||
222 | { | ||
223 | GNUNET_free (buf); | ||
224 | GNUNET_free (fn); | ||
225 | return GNUNET_SYSERR; | ||
226 | } | ||
227 | if (ranking != NULL) | ||
228 | *ranking = ntohl (((int *) buf)[0]); | ||
229 | zend = sizeof (int); | ||
230 | while ((zend < len) && (buf[zend] != '\0')) | ||
231 | zend++; | ||
232 | if (zend == len) | ||
233 | { | ||
234 | GNUNET_free (buf); | ||
235 | GNUNET_free (fn); | ||
236 | return GNUNET_SYSERR; | ||
237 | } | ||
238 | if (ns_name != NULL) | ||
239 | { | ||
240 | if (zend != sizeof (int)) | ||
241 | *ns_name = GNUNET_strdup (&buf[sizeof (int)]); | ||
242 | else | ||
243 | *ns_name = NULL; | ||
244 | } | ||
245 | zend++; | ||
246 | size = len - zend; | ||
247 | if (meta != NULL) | ||
248 | { | ||
249 | *meta = GNUNET_CONTAINER_meta_data_deserialize (&buf[zend], size); | ||
250 | if ((*meta) == NULL) | ||
251 | { | ||
252 | /* invalid data! remove! */ | ||
253 | GNUNET_break (0); | ||
254 | if (0 != UNLINK (fn)) | ||
255 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, | ||
256 | "unlink", fn); | ||
257 | GNUNET_free (buf); | ||
258 | GNUNET_free (fn); | ||
259 | return GNUNET_SYSERR; | ||
260 | } | ||
261 | } | ||
262 | GNUNET_free (fn); | ||
263 | GNUNET_free (buf); | ||
264 | return GNUNET_OK; | ||
265 | } | ||
266 | |||
267 | |||
268 | |||
269 | /** | ||
270 | * Return the unique, human readable name for the given namespace. | ||
271 | * | ||
272 | * @return NULL on failure (should never happen) | ||
273 | */ | ||
274 | char * | ||
275 | GNUNET_PSEUDONYM_id_to_name (struct GNUNET_CONFIGURATION_Handle *cfg, | ||
276 | const GNUNET_HashCode * nsid) | ||
277 | { | ||
278 | struct GNUNET_CONTAINER_MetaData *meta; | ||
279 | char *name; | ||
280 | GNUNET_HashCode nh; | ||
281 | char *fn; | ||
282 | unsigned long long len; | ||
283 | int fd; | ||
284 | unsigned int i; | ||
285 | unsigned int idx; | ||
286 | char *ret; | ||
287 | struct stat sbuf; | ||
288 | |||
289 | meta = NULL; | ||
290 | name = NULL; | ||
291 | if (GNUNET_OK == read_info (cfg, nsid, &meta, NULL, &name)) | ||
292 | { | ||
293 | if ((meta != NULL) && (name == NULL)) | ||
294 | name = GNUNET_CONTAINER_meta_data_get_first_by_types (meta, | ||
295 | EXTRACTOR_TITLE, | ||
296 | EXTRACTOR_FILENAME, | ||
297 | EXTRACTOR_DESCRIPTION, | ||
298 | EXTRACTOR_SUBJECT, | ||
299 | EXTRACTOR_PUBLISHER, | ||
300 | EXTRACTOR_AUTHOR, | ||
301 | EXTRACTOR_COMMENT, | ||
302 | EXTRACTOR_SUMMARY, | ||
303 | EXTRACTOR_OWNER, | ||
304 | -1); | ||
305 | if (meta != NULL) | ||
306 | { | ||
307 | GNUNET_CONTAINER_meta_data_destroy (meta); | ||
308 | meta = NULL; | ||
309 | } | ||
310 | } | ||
311 | if (name == NULL) | ||
312 | name = GNUNET_strdup (_("no-name")); | ||
313 | GNUNET_CRYPTO_hash (name, strlen (name), &nh); | ||
314 | fn = get_data_filename (cfg, PS_NAMES_DIR, &nh); | ||
315 | GNUNET_assert (fn != NULL); | ||
316 | |||
317 | len = 0; | ||
318 | if (0 == STAT (fn, &sbuf)) | ||
319 | GNUNET_DISK_file_size (fn, &len, GNUNET_YES); | ||
320 | fd = GNUNET_DISK_file_open (fn, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); | ||
321 | i = 0; | ||
322 | idx = -1; | ||
323 | while ((len >= sizeof (GNUNET_HashCode)) && | ||
324 | (sizeof (GNUNET_HashCode) | ||
325 | == READ (fd, &nh, sizeof (GNUNET_HashCode)))) | ||
326 | { | ||
327 | if (0 == memcmp (&nh, nsid, sizeof (GNUNET_HashCode))) | ||
328 | { | ||
329 | idx = i; | ||
330 | break; | ||
331 | } | ||
332 | i++; | ||
333 | len -= sizeof (GNUNET_HashCode); | ||
334 | } | ||
335 | if (idx == -1) | ||
336 | { | ||
337 | idx = i; | ||
338 | if (sizeof (GNUNET_HashCode) != | ||
339 | WRITE (fd, nsid, sizeof (GNUNET_HashCode))) | ||
340 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "write", fn); | ||
341 | } | ||
342 | CLOSE (fd); | ||
343 | ret = GNUNET_malloc (strlen (name) + 32); | ||
344 | GNUNET_snprintf (ret, strlen (name) + 32, "%s-%u", name, idx); | ||
345 | GNUNET_free (name); | ||
346 | GNUNET_free (fn); | ||
347 | return ret; | ||
348 | } | ||
349 | |||
350 | /** | ||
351 | * Get the namespace ID belonging to the given namespace name. | ||
352 | * | ||
353 | * @return GNUNET_OK on success | ||
354 | */ | ||
355 | int | ||
356 | GNUNET_PSEUDONYM_name_to_id (struct GNUNET_CONFIGURATION_Handle *cfg, | ||
357 | const char *ns_uname, GNUNET_HashCode * nsid) | ||
358 | { | ||
359 | size_t slen; | ||
360 | unsigned long long len; | ||
361 | unsigned int idx; | ||
362 | char *name; | ||
363 | GNUNET_HashCode nh; | ||
364 | char *fn; | ||
365 | int fd; | ||
366 | |||
367 | idx = -1; | ||
368 | slen = strlen (ns_uname); | ||
369 | while ((slen > 0) && (1 != sscanf (&ns_uname[slen - 1], "-%u", &idx))) | ||
370 | slen--; | ||
371 | if (slen == 0) | ||
372 | return GNUNET_SYSERR; | ||
373 | name = GNUNET_strdup (ns_uname); | ||
374 | name[slen - 1] = '\0'; | ||
375 | GNUNET_CRYPTO_hash (name, strlen (name), &nh); | ||
376 | GNUNET_free (name); | ||
377 | fn = get_data_filename (cfg, PS_NAMES_DIR, &nh); | ||
378 | GNUNET_assert (fn != NULL); | ||
379 | |||
380 | if ((GNUNET_OK != GNUNET_DISK_file_test (fn) || | ||
381 | (GNUNET_OK != GNUNET_DISK_file_size (fn, &len, GNUNET_YES))) || | ||
382 | ((idx + 1) * sizeof (GNUNET_HashCode) > len)) | ||
383 | { | ||
384 | GNUNET_free (fn); | ||
385 | return GNUNET_SYSERR; | ||
386 | } | ||
387 | fd = GNUNET_DISK_file_open (fn, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); | ||
388 | GNUNET_free (fn); | ||
389 | LSEEK (fd, idx * sizeof (GNUNET_HashCode), SEEK_SET); | ||
390 | if (sizeof (GNUNET_HashCode) != READ (fd, nsid, sizeof (GNUNET_HashCode))) | ||
391 | { | ||
392 | CLOSE (fd); | ||
393 | return GNUNET_SYSERR; | ||
394 | } | ||
395 | CLOSE (fd); | ||
396 | return GNUNET_OK; | ||
397 | } | ||
398 | |||
399 | |||
400 | |||
401 | |||
402 | struct ListPseudonymClosure | ||
403 | { | ||
404 | GNUNET_PSEUDONYM_Iterator iterator; | ||
405 | void *closure; | ||
406 | struct GNUNET_CONFIGURATION_Handle *cfg; | ||
407 | }; | ||
408 | |||
409 | static int | ||
410 | list_pseudonym_helper (void *cls, const char *fullname) | ||
411 | { | ||
412 | struct ListPseudonymClosure *c = cls; | ||
413 | int ret; | ||
414 | GNUNET_HashCode id; | ||
415 | int rating; | ||
416 | struct GNUNET_CONTAINER_MetaData *meta; | ||
417 | const char *fn; | ||
418 | |||
419 | if (strlen (fullname) < sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)) | ||
420 | return GNUNET_OK; | ||
421 | fn = | ||
422 | &fullname[strlen (fullname) + 1 - | ||
423 | sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)]; | ||
424 | if (fn[-1] != DIR_SEPARATOR) | ||
425 | return GNUNET_OK; | ||
426 | ret = GNUNET_OK; | ||
427 | if (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (fn, &id)) | ||
428 | return GNUNET_OK; /* invalid name */ | ||
429 | if (GNUNET_OK != read_info (c->cfg, &id, &meta, &rating, NULL)) | ||
430 | return GNUNET_OK; /* ignore entry */ | ||
431 | if (c->iterator != NULL) | ||
432 | ret = c->iterator (c->closure, &id, meta, rating); | ||
433 | GNUNET_CONTAINER_meta_data_destroy (meta); | ||
434 | return ret; | ||
435 | } | ||
436 | |||
437 | /** | ||
438 | * List all available pseudonyms. | ||
439 | */ | ||
440 | int | ||
441 | GNUNET_PSEUDONYM_list_all (struct GNUNET_CONFIGURATION_Handle *cfg, | ||
442 | GNUNET_PSEUDONYM_Iterator iterator, void *closure) | ||
443 | { | ||
444 | struct ListPseudonymClosure cls; | ||
445 | char *fn; | ||
446 | int ret; | ||
447 | |||
448 | cls.iterator = iterator; | ||
449 | cls.closure = closure; | ||
450 | cls.cfg = cfg; | ||
451 | fn = get_data_filename (cfg, PS_METADATA_DIR, NULL); | ||
452 | GNUNET_assert (fn != NULL); | ||
453 | GNUNET_DISK_directory_create (fn); | ||
454 | ret = GNUNET_DISK_directory_scan (fn, &list_pseudonym_helper, &cls); | ||
455 | GNUNET_free (fn); | ||
456 | return ret; | ||
457 | } | ||
458 | |||
459 | /** | ||
460 | * Change the ranking of a pseudonym. | ||
461 | * | ||
462 | * @param nsid id of the pseudonym | ||
463 | * @param delta by how much should the rating be | ||
464 | * changed? | ||
465 | * @return new rating of the pseudonym | ||
466 | */ | ||
467 | int | ||
468 | GNUNET_PSEUDONYM_rank (struct GNUNET_CONFIGURATION_Handle *cfg, | ||
469 | const GNUNET_HashCode * nsid, int delta) | ||
470 | { | ||
471 | struct GNUNET_CONTAINER_MetaData *meta; | ||
472 | int ret; | ||
473 | int32_t ranking; | ||
474 | char *name; | ||
475 | |||
476 | name = NULL; | ||
477 | ret = read_info (cfg, nsid, &meta, &ranking, &name); | ||
478 | if (ret == GNUNET_SYSERR) | ||
479 | { | ||
480 | ranking = 0; | ||
481 | meta = GNUNET_CONTAINER_meta_data_create (); | ||
482 | } | ||
483 | ranking += delta; | ||
484 | write_pseudonym_info (cfg, nsid, meta, ranking, name); | ||
485 | GNUNET_CONTAINER_meta_data_destroy (meta); | ||
486 | GNUNET_free_non_null (name); | ||
487 | return ranking; | ||
488 | } | ||
489 | |||
490 | /** | ||
491 | * Insert metadata into existing MD record (passed as cls). | ||
492 | */ | ||
493 | static int | ||
494 | merge_meta_helper (EXTRACTOR_KeywordType type, const char *data, void *cls) | ||
495 | { | ||
496 | struct GNUNET_CONTAINER_MetaData *meta = cls; | ||
497 | GNUNET_CONTAINER_meta_data_insert (meta, type, data); | ||
498 | return GNUNET_OK; | ||
499 | } | ||
500 | |||
501 | |||
502 | |||
503 | /** | ||
504 | * Add a pseudonym to the set of known pseudonyms. | ||
505 | * For all pseudonym advertisements that we discover | ||
506 | * FSUI should automatically call this function. | ||
507 | * | ||
508 | * @param id the pseudonym identifier | ||
509 | */ | ||
510 | void | ||
511 | GNUNET_PSEUDONYM_add (struct GNUNET_CONFIGURATION_Handle *cfg, | ||
512 | const GNUNET_HashCode * id, | ||
513 | const struct GNUNET_CONTAINER_MetaData *meta) | ||
514 | { | ||
515 | char *name; | ||
516 | int32_t ranking; | ||
517 | struct GNUNET_CONTAINER_MetaData *old; | ||
518 | char *fn; | ||
519 | struct stat sbuf; | ||
520 | |||
521 | ranking = 0; | ||
522 | fn = get_data_filename (cfg, PS_METADATA_DIR, id); | ||
523 | GNUNET_assert (fn != NULL); | ||
524 | |||
525 | if ((0 == STAT (fn, &sbuf)) && | ||
526 | (GNUNET_OK == read_info (cfg, id, &old, &ranking, &name))) | ||
527 | { | ||
528 | GNUNET_CONTAINER_meta_data_get_contents (meta, &merge_meta_helper, old); | ||
529 | write_pseudonym_info (cfg, id, old, ranking, name); | ||
530 | GNUNET_CONTAINER_meta_data_destroy (old); | ||
531 | GNUNET_free_non_null (name); | ||
532 | } | ||
533 | else | ||
534 | { | ||
535 | write_pseudonym_info (cfg, id, meta, ranking, NULL); | ||
536 | } | ||
537 | GNUNET_free (fn); | ||
538 | internal_notify (id, meta, ranking); | ||
539 | } | ||
540 | |||
541 | |||
542 | |||
543 | |||
544 | |||
545 | /* end of pseudonym.c */ | ||