052b36d0 |
1 | /* -*-c-*- |
2 | * |
3688eb75 |
3 | * $Id$ |
052b36d0 |
4 | * |
5 | * Manipulating key data |
6 | * |
7 | * (c) 1999 Straylight/Edgeware |
8 | */ |
9 | |
10 | /*----- Licensing notice --------------------------------------------------* |
11 | * |
12 | * This file is part of Catacomb. |
13 | * |
14 | * Catacomb is free software; you can redistribute it and/or modify |
15 | * it under the terms of the GNU Library General Public License as |
16 | * published by the Free Software Foundation; either version 2 of the |
17 | * License, or (at your option) any later version. |
18 | * |
19 | * Catacomb is distributed in the hope that it will be useful, |
20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
22 | * GNU Library General Public License for more details. |
23 | * |
24 | * You should have received a copy of the GNU Library General Public |
25 | * License along with Catacomb; if not, write to the Free |
26 | * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, |
27 | * MA 02111-1307, USA. |
28 | */ |
29 | |
052b36d0 |
30 | #ifndef CATACOMB_KEY_DATA_H |
31 | #define CATACOMB_KEY_DATA_H |
32 | |
33 | #ifdef __cplusplus |
34 | extern "C" { |
35 | #endif |
36 | |
37 | /*----- Header files ------------------------------------------------------*/ |
38 | |
39 | #include <stddef.h> |
40 | |
41 | #include <mLib/bits.h> |
42 | #include <mLib/dstr.h> |
43 | #include <mLib/sym.h> |
44 | |
1dda051b |
45 | #ifndef CATACOMB_KEY_ERROR_H |
46 | # include "key-error.h" |
47 | #endif |
48 | |
052b36d0 |
49 | #ifndef CATACOMB_MP_H |
50 | # include "mp.h" |
51 | #endif |
52 | |
1ba83484 |
53 | #ifndef CATACOMB_EC_H |
54 | # include "ec.h" |
55 | #endif |
56 | |
052b36d0 |
57 | /*----- Data structures ---------------------------------------------------*/ |
58 | |
59 | /* --- Key binary data --- */ |
60 | |
61 | typedef struct key_bin { |
62 | octet *k; /* Pointer to key data */ |
63 | size_t sz; /* Size of the key data (in bytes) */ |
64 | } key_bin; |
65 | |
66 | /* --- Key data structure --- */ |
67 | |
68 | typedef struct key_data { |
69 | unsigned e; /* Encoding type for key data */ |
ef13e9a4 |
70 | unsigned ref; /* Reference counter */ |
052b36d0 |
71 | union { |
72 | key_bin k; /* Binary key data */ |
73 | mp *m; /* Multiprecision integer */ |
74 | sym_table s; /* Structured key data */ |
1ba83484 |
75 | char *p; /* String pointer */ |
76 | ec e; /* Elliptic curve point */ |
052b36d0 |
77 | } u; |
78 | } key_data; |
79 | |
80 | typedef struct key_struct { |
81 | sym_base _b; |
ef13e9a4 |
82 | key_data *k; |
052b36d0 |
83 | } key_struct; |
84 | |
ef13e9a4 |
85 | typedef struct key_subkeyiter { sym_iter i; } key_subkeyiter; |
86 | |
3f6ded6a |
87 | /* --- Packing and unpacking --- */ |
88 | |
89 | typedef struct key_packdef { |
ef13e9a4 |
90 | unsigned e; /* Key data encoding type */ |
3f6ded6a |
91 | void *p; /* Pointer to the destination */ |
ef13e9a4 |
92 | key_data *kd; /* Key data block */ |
3f6ded6a |
93 | } key_packdef; |
94 | |
95 | typedef struct key_packstruct { |
96 | char *name; /* Pointer to name string */ |
97 | key_packdef kp; /* Packing structure */ |
98 | } key_packstruct; |
99 | |
052b36d0 |
100 | /* --- Key binary encoding --- * |
101 | * |
102 | * The binary encoding consists of a header containing a 16-bit encoding type |
103 | * and a 16-bit length, followed immediately by the key data, followed by |
104 | * between zero and three zero bytes to make the total length a multiple of |
105 | * four. The format of the following data depends on the encoding type: |
106 | * |
107 | * @KENC_BINARY@ Binary data. |
108 | * |
109 | * @KENC_MP@ Octet array interpreted in big-endian byte order. |
110 | * |
111 | * @KENC_STRUCT@ An array of pairs, each containing a string (8-bit |
112 | * length followed by data and zero-padding to 4-byte |
113 | * boundary) and key binary encodings. |
114 | * |
115 | * @KENC_ENCRYPT@ Binary data, format |
116 | */ |
117 | |
118 | /* --- Key encoding methods and other flags--- */ |
119 | |
120 | enum { |
121 | |
122 | /* --- Bottom two bits are the encoding type --- */ |
123 | |
1ba83484 |
124 | KF_ENCMASK = 0x83, /* Encoding mask */ |
052b36d0 |
125 | KENC_BINARY = 0x00, /* Plain binary key (@k@) */ |
126 | KENC_MP = 0x01, /* Multiprecision integer (@i@) */ |
127 | KENC_STRUCT = 0x02, /* Structured key data (@s@) */ |
128 | KENC_ENCRYPT = 0x03, /* Encrypted key type (@k@) */ |
1ba83484 |
129 | KENC_STRING = 0x80, /* ASCII string (@p@) */ |
130 | KENC_EC = 0x81, /* Elliptic curve point (@e@) */ |
052b36d0 |
131 | |
132 | /* --- Key category bits --- */ |
133 | |
134 | KF_CATMASK = 0x0c, /* Category mask */ |
135 | KCAT_SYMM = 0x00, /* Symmetric encryption key */ |
136 | KCAT_PRIV = 0x04, /* Private (asymmetric) key */ |
137 | KCAT_PUB = 0x08, /* Public (asymmetric) key */ |
138 | KCAT_SHARE = 0x0c, /* Shared (asymmetric) key */ |
139 | KF_NONSECRET = 0x08, /* Bit flag for non-secret keys */ |
140 | |
141 | /* --- Other flags --- */ |
142 | |
143 | KF_BURN = 0x10, /* Burn key after use */ |
ef13e9a4 |
144 | KF_OPT = 0x20, /* Optional key (for @key_unpack@) */ |
052b36d0 |
145 | |
146 | /* --- Tag end --- */ |
147 | |
148 | KENC_MAX /* Dummy limit constant */ |
149 | }; |
150 | |
1dda051b |
151 | /* --- Key locking return codes --- */ |
152 | |
153 | #define KL_OK 0 /* All good */ |
154 | #define KL_IOERR -1 /* I/O problem (e.g., getting pp) */ |
155 | #define KL_KEYERR -2 /* Wrong key supplied */ |
156 | #define KL_DATAERR -3 /* Data format error */ |
157 | |
052b36d0 |
158 | /* --- Key flag filtering --- */ |
159 | |
160 | typedef struct key_filter { |
161 | unsigned f; |
162 | unsigned m; |
163 | } key_filter; |
164 | |
165 | /* --- Matching aginst key selection --- */ |
166 | |
167 | #define KEY_MATCH(kd, kf) \ |
168 | (!(kf) || \ |
169 | ((kd)->e & KF_ENCMASK) == KENC_STRUCT || \ |
170 | ((kd)->e & (kf)->m) == (kf)->f) |
171 | |
172 | /*----- Key flags and filtering -------------------------------------------*/ |
173 | |
174 | /* --- @key_readflags@ --- * |
175 | * |
176 | * Arguments: @const char *p@ = pointer to string to read |
177 | * @char **pp@ = where to store the end pointer |
178 | * @unsigned *ff@ = where to store the flags |
179 | * @unsigned *mm@ = where to store the mask |
180 | * |
181 | * Returns: Zero if all went well, nonzero if there was an error. |
182 | * |
183 | * Use: Reads a flag string. |
184 | */ |
185 | |
186 | extern int key_readflags(const char */*p*/, char **/*pp*/, |
187 | unsigned */*ff*/, unsigned */*mm*/); |
188 | |
189 | /* --- @key_writeflags@ --- * |
190 | * |
191 | * Arguments: @unsigned f@ = flags to write |
192 | * @dstr *d@ = pointer to destination string |
193 | * |
194 | * Returns: --- |
195 | * |
196 | * Use: Emits a flags word as a string representation. |
197 | */ |
198 | |
199 | extern void key_writeflags(unsigned /*f*/, dstr */*d*/); |
200 | |
201 | /* --- @key_match@ --- * |
202 | * |
203 | * Arguments: @key_data *k@ = pointer to key data block |
204 | * @const key_filter *kf@ = pointer to filter block |
205 | * |
206 | * Returns: Nonzero if the key matches the filter. |
207 | * |
208 | * Use: Checks whether a key matches a filter. |
209 | */ |
210 | |
211 | extern int key_match(key_data */*k*/, const key_filter */*kf*/); |
212 | |
213 | /*----- Setting new key data ----------------------------------------------*/ |
214 | |
ef13e9a4 |
215 | /* --- @key_newraw@ --- * |
052b36d0 |
216 | * |
ef13e9a4 |
217 | * Arguments: @unsigned e@ = encoding type to set |
218 | * |
219 | * Returns: New key block, not filled in. |
220 | */ |
221 | |
222 | extern key_data *key_newraw(unsigned /*e*/); |
223 | |
224 | /* --- @key_newbinary@ --- * |
225 | * |
226 | * Arguments: @unsigned e@ = other encoding flags |
052b36d0 |
227 | * @const void *p@ = pointer to key data |
228 | * @size_t sz@ = size of the key data |
229 | * |
ef13e9a4 |
230 | * Returns: New key data object. |
052b36d0 |
231 | */ |
232 | |
ef13e9a4 |
233 | extern key_data *key_newbinary(unsigned /*e*/, |
234 | const void */*p*/, size_t /*sz*/); |
052b36d0 |
235 | |
ef13e9a4 |
236 | /* --- @key_newencrypted@ --- * |
052b36d0 |
237 | * |
ef13e9a4 |
238 | * Arguments: @unsigned e@ = other encoding flags |
052b36d0 |
239 | * @const void *p@ = pointer to key data |
240 | * @size_t sz@ = size of the key data |
241 | * |
ef13e9a4 |
242 | * Returns: New key data object. |
052b36d0 |
243 | */ |
244 | |
ef13e9a4 |
245 | extern key_data *key_newencrypted(unsigned /*e*/, |
246 | const void */*p*/, size_t /*sz*/); |
052b36d0 |
247 | |
ef13e9a4 |
248 | /* --- @key_mewmp@ --- * |
052b36d0 |
249 | * |
ef13e9a4 |
250 | * Arguments: @unsigned e@ = other encoding flags |
052b36d0 |
251 | * @mp *m@ = pointer to the value to set |
252 | * |
ef13e9a4 |
253 | * Returns: New key data object. |
052b36d0 |
254 | */ |
255 | |
ef13e9a4 |
256 | extern key_data *key_newmp(unsigned /*e*/, mp */*m*/); |
052b36d0 |
257 | |
ef13e9a4 |
258 | /* --- @key_newstring@ --- * |
1ba83484 |
259 | * |
ef13e9a4 |
260 | * Arguments: @unsigned e@ = other encoding flags |
1ba83484 |
261 | * @const char *p@ = pointer to the value to set |
262 | * |
ef13e9a4 |
263 | * Returns: New key data object. |
1ba83484 |
264 | */ |
265 | |
ef13e9a4 |
266 | extern key_data *key_newstring(unsigned /*e*/, const char */*p*/); |
1ba83484 |
267 | |
ef13e9a4 |
268 | /* --- @key_newec@ --- * |
1ba83484 |
269 | * |
ef13e9a4 |
270 | * Arguments: @unsigned e@ = other encoding flags |
271 | * @const ec *pt@ = pointer to the value to set |
1ba83484 |
272 | * |
ef13e9a4 |
273 | * Returns: New key data object. |
1ba83484 |
274 | */ |
275 | |
ef13e9a4 |
276 | extern key_data *key_newec(unsigned /*e*/, const ec */*pt*/); |
1ba83484 |
277 | |
ef13e9a4 |
278 | /* --- @key_newstruct@ --- * |
052b36d0 |
279 | * |
ef13e9a4 |
280 | * Arguments: --- |
052b36d0 |
281 | * |
ef13e9a4 |
282 | * Returns: New key data object. |
052b36d0 |
283 | */ |
284 | |
ef13e9a4 |
285 | extern key_data *key_newstruct(void); |
052b36d0 |
286 | |
287 | /* --- @key_structfind@ --- * |
288 | * |
289 | * Arguments: @key_data *k@ = pointer to key data block |
290 | * @const char *tag@ = pointer to tag string |
291 | * |
292 | * Returns: Pointer to key data block, or null. |
293 | * |
294 | * Use: Looks up the tag in a structured key. |
295 | */ |
296 | |
297 | extern key_data *key_structfind(key_data */*k*/, const char */*tag*/); |
298 | |
ef13e9a4 |
299 | /* --- @key_mksubkeyiter@ --- * |
300 | * |
301 | * Arguments: @key_subkeyiter *i@ = pointer to iterator block |
302 | * @key_data *k@ = pointer to key data block |
303 | * |
304 | * Returns: --- |
305 | * |
306 | * Use: Initializes a subkey iterator. |
307 | */ |
308 | |
309 | extern void key_mksubkeyiter(key_subkeyiter */*i*/, key_data */*k*/); |
310 | |
311 | /* --- @key_nextsubkey@ --- * |
312 | * |
313 | * Arguments: @key_structiter *i@ = pointer to iterator block |
314 | * @const char **tag@ = where to put the tag pointer, or null |
315 | * @key_data **kd@ = where to put the key data pointer, or null |
316 | * |
317 | * Returns: Nonzero if there was another item, zero if we hit the |
318 | * end-stop. |
319 | * |
320 | * Use: Collects the next subkey of a structured key. |
321 | */ |
322 | |
323 | extern int key_nextsubkey(key_subkeyiter */*i*/, |
324 | const char **/*tag*/, key_data **/*kd*/); |
325 | |
326 | /* --- @key_structset@, @key_structsteal@ --- * |
052b36d0 |
327 | * |
328 | * Arguments: @key_data *k@ = pointer to key data block |
329 | * @const char *tag@ = pointer to tag string |
ef13e9a4 |
330 | * @key_data *kd@ = new key data to store |
052b36d0 |
331 | * |
ef13e9a4 |
332 | * Returns: --- |
052b36d0 |
333 | * |
ef13e9a4 |
334 | * Use: Creates a new subkey. Stealing doesn't affect @kd@'s |
335 | * refcount. If @kd@ is null, the subkey is deleted. |
052b36d0 |
336 | */ |
337 | |
ef13e9a4 |
338 | extern void key_structset(key_data */*k*/, |
339 | const char */*tag*/, key_data */*kd*/); |
340 | extern void key_structsteal(key_data */*k*/, |
341 | const char */*tag*/, key_data */*kd*/); |
342 | |
343 | /* --- @key_split@ --- * |
344 | * |
345 | * Arguments: @key_data **kk@ = address of pointer to key data block |
346 | * |
347 | * Returns: --- |
348 | * |
349 | * Use: Replaces @*kk@ with a pointer to the same key data, but with |
350 | * just one reference. |
351 | */ |
352 | |
353 | extern void key_split(key_data **/*kk*/); |
052b36d0 |
354 | |
355 | /*----- Miscellaneous operations ------------------------------------------*/ |
356 | |
ef13e9a4 |
357 | /* --- @key_incref@ --- * |
358 | * |
359 | * Arguments: @key_data *k@ = pointer to key data |
360 | * |
361 | * Returns: --- |
362 | * |
363 | * Use: Increments the refcount on a key data block. |
364 | */ |
365 | |
366 | #define KEY_INCREF(k) ((k)->ref++) |
367 | extern void key_incref(key_data */*k*/); |
368 | |
052b36d0 |
369 | /* --- @key_destroy@ --- * |
370 | * |
371 | * Arguments: @key_data *k@ = pointer to key data to destroy |
372 | * |
373 | * Returns: --- |
374 | * |
ef13e9a4 |
375 | * Use: Destroys a block of key data, regardless of reference count. |
376 | * Don't use this unless you know what you're doing. |
052b36d0 |
377 | */ |
378 | |
379 | extern void key_destroy(key_data */*k*/); |
380 | |
ef13e9a4 |
381 | /* --- @key_drop@ --- * |
382 | * |
383 | * Arguments: @key_data *k@ = pointer to key data to destroy |
384 | * |
385 | * Returns: --- |
386 | * |
387 | * Use: Drops a reference to key data, destroying it if necessary. |
388 | */ |
389 | |
390 | #define KEY_DROP(k) do { \ |
391 | key_data *_k = k; \ |
392 | _k->ref--; \ |
393 | if (_k->ref == 0) \ |
394 | key_destroy(_k); \ |
395 | } while (0) |
396 | |
397 | extern void key_drop(key_data */*k*/); |
398 | |
052b36d0 |
399 | /* --- @key_do@ --- * |
400 | * |
401 | * Arguments: @key_data *k@ = pointer to key data block |
402 | * @const key_filter *kf@ = pointer to filter block |
403 | * @dstr *d@ = pointer to base string |
404 | * @int (*func)(key_data *kd, dstr *d, void *p@ = function |
405 | * @void *p@ = argument to function |
406 | * |
407 | * Returns: Nonzero return code from function, or zero. |
408 | * |
409 | * Use: Runs a function over all the leaves of a key. |
410 | */ |
411 | |
412 | extern int key_do(key_data */*k*/, const key_filter */*kf*/, dstr */*d*/, |
413 | int (*/*func*/)(key_data */*kd*/, |
414 | dstr */*d*/, void */*p*/), |
415 | void */*p*/); |
416 | |
052b36d0 |
417 | /*----- Textual encoding --------------------------------------------------*/ |
418 | |
419 | /* --- @key_read@ --- * |
420 | * |
421 | * Arguments: @const char *p@ = pointer to textual key representation |
052b36d0 |
422 | * @char **pp@ = where to store the end pointer |
423 | * |
ef13e9a4 |
424 | * Returns: The newly-read key data, or null if it failed. |
052b36d0 |
425 | * |
426 | * Use: Parses a textual key description. |
427 | */ |
428 | |
ef13e9a4 |
429 | extern key_data *key_read(const char */*p*/, char **/*pp*/); |
052b36d0 |
430 | |
431 | /* --- @key_write@ --- * |
432 | * |
433 | * Arguments: @key_data *k@ = pointer to key data |
434 | * @dstr *d@ = destination string to write on |
435 | * @const key_filter *kf@ = pointer to key selection block |
436 | * |
437 | * Returns: Nonzero if any items were actually written. |
438 | * |
439 | * Use: Writes a key in a textual encoding. |
440 | */ |
441 | |
ef13e9a4 |
442 | extern int key_write(key_data */*k*/, dstr */*d*/, const key_filter */*kf*/); |
052b36d0 |
443 | |
444 | /*----- Key binary encoding -----------------------------------------------*/ |
445 | |
446 | /* --- @key_decode@ --- * |
447 | * |
448 | * Arguments: @const void *p@ = pointer to buffer to read |
449 | * @size_t sz@ = size of the buffer |
052b36d0 |
450 | * |
ef13e9a4 |
451 | * Returns: The newly-read key data, or null if it failed. |
052b36d0 |
452 | * |
453 | * Use: Decodes a binary representation of a key. |
454 | */ |
455 | |
ef13e9a4 |
456 | extern key_data *key_decode(const void */*p*/, size_t /*sz*/); |
052b36d0 |
457 | |
458 | /* --- @key_encode@ --- * |
459 | * |
460 | * Arguments: @key_data *k@ = pointer to key data block |
461 | * @dstr *d@ = pointer to destination string |
462 | * @const key_filter *kf@ = pointer to key selection block |
463 | * |
464 | * Returns: Nonzero if any items were actually written. |
465 | * |
466 | * Use: Encodes a key block as binary data. |
467 | */ |
468 | |
469 | extern int key_encode(key_data */*k*/, dstr */*d*/, |
470 | const key_filter */*kf*/); |
471 | |
3f6ded6a |
472 | /*----- Packing and unpacking keys ----------------------------------------*/ |
473 | |
474 | /* --- @key_pack@ --- * |
475 | * |
476 | * Arguments: @key_packdef *kp@ = pointer to packing structure |
ef13e9a4 |
477 | * @key_data **kd@ = where to put the key data pointer |
3f6ded6a |
478 | * @dstr *d@ = pointer to tag string for the key data |
479 | * |
480 | * Returns: Error code, or zero. |
481 | * |
482 | * Use: Packs a key from a data structure. |
483 | */ |
484 | |
ef13e9a4 |
485 | extern int key_pack(key_packdef */*kp*/, key_data **/*kd*/, dstr */*d*/); |
3f6ded6a |
486 | |
487 | /* --- @key_unpack@ --- * |
488 | * |
489 | * Arguments: @key_packdef *kp@ = pointer to packing structure |
490 | * @key_data *kd@ = pointer to source key data |
491 | * @dstr *d@ = pointer to tag string for the key data |
492 | * |
493 | * Returns: Error code, or zero. |
494 | * |
495 | * Use: Unpacks a key into an appropriate data structure. |
496 | */ |
497 | |
498 | extern int key_unpack(key_packdef */*kp*/, key_data */*kd*/, dstr */*d*/); |
499 | |
500 | /* --- @key_unpackdone@ --- * |
501 | * |
502 | * Arguments: @key_packdef *kp@ = pointer to packing definition |
503 | * |
504 | * Returns: --- |
505 | * |
506 | * Use: Frees the key components contained within a packing |
507 | * definition, created during key unpacking. |
508 | */ |
509 | |
510 | extern void key_unpackdone(key_packdef */*kp*/); |
511 | |
1dda051b |
512 | /*----- Key encryption ----------------------------------------------------*/ |
513 | |
514 | /* --- @key_lock@ --- * |
515 | * |
ef13e9a4 |
516 | * Arguments: @key_data **kt@ = where to store the destination pointer |
517 | * @key_data *k@ = source key data block or null to use @*kt@ |
1dda051b |
518 | * @const void *e@ = secret to encrypt key with |
519 | * @size_t esz@ = size of the secret |
520 | * |
521 | * Returns: --- |
522 | * |
523 | * Use: Encrypts a key data block using a secret. |
524 | */ |
525 | |
ef13e9a4 |
526 | extern void key_lock(key_data **/*kt*/, key_data */*k*/, |
1dda051b |
527 | const void */*e*/, size_t /*esz*/); |
528 | |
529 | /* --- @key_unlock@ --- * |
530 | * |
ef13e9a4 |
531 | * Arguments: @key_data **kt@ = where to store the destination pointer |
532 | * @key_data *k@ = source key data block or null to use @*kt@ |
1dda051b |
533 | * @const void *e@ = secret to decrypt the block with |
534 | * @size_t esz@ = size of the secret |
535 | * |
536 | * Returns: Zero for success, or a @KERR_@ error code. |
537 | * |
538 | * Use: Unlocks a key using a secret. |
539 | */ |
540 | |
ef13e9a4 |
541 | extern int key_unlock(key_data **/*kt*/, key_data */*k*/, |
1dda051b |
542 | const void */*e*/, size_t /*esz*/); |
052b36d0 |
543 | |
544 | /* --- @key_plock@ --- * |
545 | * |
ef13e9a4 |
546 | * Arguments: @key_data **kt@ = where to store the destination pointer |
547 | * @key_data *k@ = source key data block or null to use @*kt@ |
548 | * @const char *tag@ = tag to use for passphrase |
052b36d0 |
549 | * |
1dda051b |
550 | * Returns: Zero if successful, a @KERR@ error code on failure. |
052b36d0 |
551 | * |
552 | * Use: Locks a key by encrypting it with a passphrase. |
553 | */ |
554 | |
ef13e9a4 |
555 | extern int key_plock(key_data **/*kt*/, key_data */*k*/, |
556 | const char */*tag*/); |
052b36d0 |
557 | |
558 | /* --- @key_punlock@ --- * |
559 | * |
ef13e9a4 |
560 | * Arguments: @key_data **kt@ = where to store the destination pointer |
561 | * @key_data *k@ = source key data block or null to use @*kt@ |
562 | * @const char *tag@ = tag to use for passphrase |
052b36d0 |
563 | * |
1dda051b |
564 | * Returns: Zero if successful, a @KERR@ error code on failure. |
052b36d0 |
565 | * |
566 | * Use: Unlocks a passphrase-locked key. |
567 | */ |
568 | |
ef13e9a4 |
569 | extern int key_punlock(key_data **/*kt*/, key_data */*k*/, |
570 | const char */*tag*/); |
052b36d0 |
571 | |
572 | /*----- That's all, folks -------------------------------------------------*/ |
573 | |
574 | #ifdef __cplusplus |
575 | } |
576 | #endif |
577 | |
578 | #endif |