1 | #include <machine/rtems-bsd-user-space.h> |
---|
2 | |
---|
3 | /* |
---|
4 | * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. |
---|
5 | * |
---|
6 | * Licensed under the OpenSSL license (the "License"). You may not use |
---|
7 | * this file except in compliance with the License. You can obtain a copy |
---|
8 | * in the file LICENSE in the source distribution or at |
---|
9 | * https://www.openssl.org/source/license.html |
---|
10 | */ |
---|
11 | |
---|
12 | #include <stdio.h> |
---|
13 | #include <string.h> |
---|
14 | #include <stdlib.h> |
---|
15 | #include "apps.h" |
---|
16 | #include "progs.h" |
---|
17 | #include <openssl/bio.h> |
---|
18 | #include <openssl/err.h> |
---|
19 | #include <openssl/evp.h> |
---|
20 | #include <openssl/objects.h> |
---|
21 | #include <openssl/x509.h> |
---|
22 | #include <openssl/pem.h> |
---|
23 | #include <openssl/hmac.h> |
---|
24 | |
---|
25 | #undef BUFSIZE |
---|
26 | #define BUFSIZE 1024*8 |
---|
27 | |
---|
28 | int do_fp(BIO *out, unsigned char *buf, BIO *bp, int sep, int binout, |
---|
29 | EVP_PKEY *key, unsigned char *sigin, int siglen, |
---|
30 | const char *sig_name, const char *md_name, |
---|
31 | const char *file); |
---|
32 | |
---|
33 | typedef enum OPTION_choice { |
---|
34 | OPT_ERR = -1, OPT_EOF = 0, OPT_HELP, |
---|
35 | OPT_C, OPT_R, OPT_OUT, OPT_SIGN, OPT_PASSIN, OPT_VERIFY, |
---|
36 | OPT_PRVERIFY, OPT_SIGNATURE, OPT_KEYFORM, OPT_ENGINE, OPT_ENGINE_IMPL, |
---|
37 | OPT_HEX, OPT_BINARY, OPT_DEBUG, OPT_FIPS_FINGERPRINT, |
---|
38 | OPT_HMAC, OPT_MAC, OPT_SIGOPT, OPT_MACOPT, |
---|
39 | OPT_DIGEST, |
---|
40 | OPT_R_ENUM |
---|
41 | } OPTION_CHOICE; |
---|
42 | |
---|
43 | const OPTIONS dgst_options[] = { |
---|
44 | {OPT_HELP_STR, 1, '-', "Usage: %s [options] [file...]\n"}, |
---|
45 | {OPT_HELP_STR, 1, '-', |
---|
46 | " file... files to digest (default is stdin)\n"}, |
---|
47 | {"help", OPT_HELP, '-', "Display this summary"}, |
---|
48 | {"c", OPT_C, '-', "Print the digest with separating colons"}, |
---|
49 | {"r", OPT_R, '-', "Print the digest in coreutils format"}, |
---|
50 | {"out", OPT_OUT, '>', "Output to filename rather than stdout"}, |
---|
51 | {"passin", OPT_PASSIN, 's', "Input file pass phrase source"}, |
---|
52 | {"sign", OPT_SIGN, 's', "Sign digest using private key"}, |
---|
53 | {"verify", OPT_VERIFY, 's', |
---|
54 | "Verify a signature using public key"}, |
---|
55 | {"prverify", OPT_PRVERIFY, 's', |
---|
56 | "Verify a signature using private key"}, |
---|
57 | {"signature", OPT_SIGNATURE, '<', "File with signature to verify"}, |
---|
58 | {"keyform", OPT_KEYFORM, 'f', "Key file format (PEM or ENGINE)"}, |
---|
59 | {"hex", OPT_HEX, '-', "Print as hex dump"}, |
---|
60 | {"binary", OPT_BINARY, '-', "Print in binary form"}, |
---|
61 | {"d", OPT_DEBUG, '-', "Print debug info"}, |
---|
62 | {"debug", OPT_DEBUG, '-', "Print debug info"}, |
---|
63 | {"fips-fingerprint", OPT_FIPS_FINGERPRINT, '-', |
---|
64 | "Compute HMAC with the key used in OpenSSL-FIPS fingerprint"}, |
---|
65 | {"hmac", OPT_HMAC, 's', "Create hashed MAC with key"}, |
---|
66 | {"mac", OPT_MAC, 's', "Create MAC (not necessarily HMAC)"}, |
---|
67 | {"sigopt", OPT_SIGOPT, 's', "Signature parameter in n:v form"}, |
---|
68 | {"macopt", OPT_MACOPT, 's', "MAC algorithm parameters in n:v form or key"}, |
---|
69 | {"", OPT_DIGEST, '-', "Any supported digest"}, |
---|
70 | OPT_R_OPTIONS, |
---|
71 | #ifndef OPENSSL_NO_ENGINE |
---|
72 | {"engine", OPT_ENGINE, 's', "Use engine e, possibly a hardware device"}, |
---|
73 | {"engine_impl", OPT_ENGINE_IMPL, '-', |
---|
74 | "Also use engine given by -engine for digest operations"}, |
---|
75 | #endif |
---|
76 | {NULL} |
---|
77 | }; |
---|
78 | |
---|
79 | int dgst_main(int argc, char **argv) |
---|
80 | { |
---|
81 | BIO *in = NULL, *inp, *bmd = NULL, *out = NULL; |
---|
82 | ENGINE *e = NULL, *impl = NULL; |
---|
83 | EVP_PKEY *sigkey = NULL; |
---|
84 | STACK_OF(OPENSSL_STRING) *sigopts = NULL, *macopts = NULL; |
---|
85 | char *hmac_key = NULL; |
---|
86 | char *mac_name = NULL; |
---|
87 | char *passinarg = NULL, *passin = NULL; |
---|
88 | const EVP_MD *md = NULL, *m; |
---|
89 | const char *outfile = NULL, *keyfile = NULL, *prog = NULL; |
---|
90 | const char *sigfile = NULL; |
---|
91 | OPTION_CHOICE o; |
---|
92 | int separator = 0, debug = 0, keyform = FORMAT_PEM, siglen = 0; |
---|
93 | int i, ret = 1, out_bin = -1, want_pub = 0, do_verify = 0; |
---|
94 | unsigned char *buf = NULL, *sigbuf = NULL; |
---|
95 | int engine_impl = 0; |
---|
96 | |
---|
97 | prog = opt_progname(argv[0]); |
---|
98 | buf = app_malloc(BUFSIZE, "I/O buffer"); |
---|
99 | md = EVP_get_digestbyname(prog); |
---|
100 | |
---|
101 | prog = opt_init(argc, argv, dgst_options); |
---|
102 | while ((o = opt_next()) != OPT_EOF) { |
---|
103 | switch (o) { |
---|
104 | case OPT_EOF: |
---|
105 | case OPT_ERR: |
---|
106 | opthelp: |
---|
107 | BIO_printf(bio_err, "%s: Use -help for summary.\n", prog); |
---|
108 | goto end; |
---|
109 | case OPT_HELP: |
---|
110 | opt_help(dgst_options); |
---|
111 | ret = 0; |
---|
112 | goto end; |
---|
113 | case OPT_C: |
---|
114 | separator = 1; |
---|
115 | break; |
---|
116 | case OPT_R: |
---|
117 | separator = 2; |
---|
118 | break; |
---|
119 | case OPT_R_CASES: |
---|
120 | if (!opt_rand(o)) |
---|
121 | goto end; |
---|
122 | break; |
---|
123 | case OPT_OUT: |
---|
124 | outfile = opt_arg(); |
---|
125 | break; |
---|
126 | case OPT_SIGN: |
---|
127 | keyfile = opt_arg(); |
---|
128 | break; |
---|
129 | case OPT_PASSIN: |
---|
130 | passinarg = opt_arg(); |
---|
131 | break; |
---|
132 | case OPT_VERIFY: |
---|
133 | keyfile = opt_arg(); |
---|
134 | want_pub = do_verify = 1; |
---|
135 | break; |
---|
136 | case OPT_PRVERIFY: |
---|
137 | keyfile = opt_arg(); |
---|
138 | do_verify = 1; |
---|
139 | break; |
---|
140 | case OPT_SIGNATURE: |
---|
141 | sigfile = opt_arg(); |
---|
142 | break; |
---|
143 | case OPT_KEYFORM: |
---|
144 | if (!opt_format(opt_arg(), OPT_FMT_ANY, &keyform)) |
---|
145 | goto opthelp; |
---|
146 | break; |
---|
147 | case OPT_ENGINE: |
---|
148 | e = setup_engine(opt_arg(), 0); |
---|
149 | break; |
---|
150 | case OPT_ENGINE_IMPL: |
---|
151 | engine_impl = 1; |
---|
152 | break; |
---|
153 | case OPT_HEX: |
---|
154 | out_bin = 0; |
---|
155 | break; |
---|
156 | case OPT_BINARY: |
---|
157 | out_bin = 1; |
---|
158 | break; |
---|
159 | case OPT_DEBUG: |
---|
160 | debug = 1; |
---|
161 | break; |
---|
162 | case OPT_FIPS_FINGERPRINT: |
---|
163 | hmac_key = "etaonrishdlcupfm"; |
---|
164 | break; |
---|
165 | case OPT_HMAC: |
---|
166 | hmac_key = opt_arg(); |
---|
167 | break; |
---|
168 | case OPT_MAC: |
---|
169 | mac_name = opt_arg(); |
---|
170 | break; |
---|
171 | case OPT_SIGOPT: |
---|
172 | if (!sigopts) |
---|
173 | sigopts = sk_OPENSSL_STRING_new_null(); |
---|
174 | if (!sigopts || !sk_OPENSSL_STRING_push(sigopts, opt_arg())) |
---|
175 | goto opthelp; |
---|
176 | break; |
---|
177 | case OPT_MACOPT: |
---|
178 | if (!macopts) |
---|
179 | macopts = sk_OPENSSL_STRING_new_null(); |
---|
180 | if (!macopts || !sk_OPENSSL_STRING_push(macopts, opt_arg())) |
---|
181 | goto opthelp; |
---|
182 | break; |
---|
183 | case OPT_DIGEST: |
---|
184 | if (!opt_md(opt_unknown(), &m)) |
---|
185 | goto opthelp; |
---|
186 | md = m; |
---|
187 | break; |
---|
188 | } |
---|
189 | } |
---|
190 | argc = opt_num_rest(); |
---|
191 | argv = opt_rest(); |
---|
192 | if (keyfile != NULL && argc > 1) { |
---|
193 | BIO_printf(bio_err, "%s: Can only sign or verify one file.\n", prog); |
---|
194 | goto end; |
---|
195 | } |
---|
196 | |
---|
197 | if (do_verify && sigfile == NULL) { |
---|
198 | BIO_printf(bio_err, |
---|
199 | "No signature to verify: use the -signature option\n"); |
---|
200 | goto end; |
---|
201 | } |
---|
202 | if (engine_impl) |
---|
203 | impl = e; |
---|
204 | |
---|
205 | in = BIO_new(BIO_s_file()); |
---|
206 | bmd = BIO_new(BIO_f_md()); |
---|
207 | if ((in == NULL) || (bmd == NULL)) { |
---|
208 | ERR_print_errors(bio_err); |
---|
209 | goto end; |
---|
210 | } |
---|
211 | |
---|
212 | if (debug) { |
---|
213 | BIO_set_callback(in, BIO_debug_callback); |
---|
214 | /* needed for windows 3.1 */ |
---|
215 | BIO_set_callback_arg(in, (char *)bio_err); |
---|
216 | } |
---|
217 | |
---|
218 | if (!app_passwd(passinarg, NULL, &passin, NULL)) { |
---|
219 | BIO_printf(bio_err, "Error getting password\n"); |
---|
220 | goto end; |
---|
221 | } |
---|
222 | |
---|
223 | if (out_bin == -1) { |
---|
224 | if (keyfile != NULL) |
---|
225 | out_bin = 1; |
---|
226 | else |
---|
227 | out_bin = 0; |
---|
228 | } |
---|
229 | |
---|
230 | out = bio_open_default(outfile, 'w', out_bin ? FORMAT_BINARY : FORMAT_TEXT); |
---|
231 | if (out == NULL) |
---|
232 | goto end; |
---|
233 | |
---|
234 | if ((!(mac_name == NULL) + !(keyfile == NULL) + !(hmac_key == NULL)) > 1) { |
---|
235 | BIO_printf(bio_err, "MAC and Signing key cannot both be specified\n"); |
---|
236 | goto end; |
---|
237 | } |
---|
238 | |
---|
239 | if (keyfile != NULL) { |
---|
240 | int type; |
---|
241 | |
---|
242 | if (want_pub) |
---|
243 | sigkey = load_pubkey(keyfile, keyform, 0, NULL, e, "key file"); |
---|
244 | else |
---|
245 | sigkey = load_key(keyfile, keyform, 0, passin, e, "key file"); |
---|
246 | if (sigkey == NULL) { |
---|
247 | /* |
---|
248 | * load_[pub]key() has already printed an appropriate message |
---|
249 | */ |
---|
250 | goto end; |
---|
251 | } |
---|
252 | type = EVP_PKEY_id(sigkey); |
---|
253 | if (type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448) { |
---|
254 | /* |
---|
255 | * We implement PureEdDSA for these which doesn't have a separate |
---|
256 | * digest, and only supports one shot. |
---|
257 | */ |
---|
258 | BIO_printf(bio_err, "Key type not supported for this operation\n"); |
---|
259 | goto end; |
---|
260 | } |
---|
261 | } |
---|
262 | |
---|
263 | if (mac_name != NULL) { |
---|
264 | EVP_PKEY_CTX *mac_ctx = NULL; |
---|
265 | int r = 0; |
---|
266 | if (!init_gen_str(&mac_ctx, mac_name, impl, 0)) |
---|
267 | goto mac_end; |
---|
268 | if (macopts != NULL) { |
---|
269 | char *macopt; |
---|
270 | for (i = 0; i < sk_OPENSSL_STRING_num(macopts); i++) { |
---|
271 | macopt = sk_OPENSSL_STRING_value(macopts, i); |
---|
272 | if (pkey_ctrl_string(mac_ctx, macopt) <= 0) { |
---|
273 | BIO_printf(bio_err, |
---|
274 | "MAC parameter error \"%s\"\n", macopt); |
---|
275 | ERR_print_errors(bio_err); |
---|
276 | goto mac_end; |
---|
277 | } |
---|
278 | } |
---|
279 | } |
---|
280 | if (EVP_PKEY_keygen(mac_ctx, &sigkey) <= 0) { |
---|
281 | BIO_puts(bio_err, "Error generating key\n"); |
---|
282 | ERR_print_errors(bio_err); |
---|
283 | goto mac_end; |
---|
284 | } |
---|
285 | r = 1; |
---|
286 | mac_end: |
---|
287 | EVP_PKEY_CTX_free(mac_ctx); |
---|
288 | if (r == 0) |
---|
289 | goto end; |
---|
290 | } |
---|
291 | |
---|
292 | if (hmac_key != NULL) { |
---|
293 | sigkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, impl, |
---|
294 | (unsigned char *)hmac_key, -1); |
---|
295 | if (sigkey == NULL) |
---|
296 | goto end; |
---|
297 | } |
---|
298 | |
---|
299 | if (sigkey != NULL) { |
---|
300 | EVP_MD_CTX *mctx = NULL; |
---|
301 | EVP_PKEY_CTX *pctx = NULL; |
---|
302 | int r; |
---|
303 | if (!BIO_get_md_ctx(bmd, &mctx)) { |
---|
304 | BIO_printf(bio_err, "Error getting context\n"); |
---|
305 | ERR_print_errors(bio_err); |
---|
306 | goto end; |
---|
307 | } |
---|
308 | if (do_verify) |
---|
309 | r = EVP_DigestVerifyInit(mctx, &pctx, md, impl, sigkey); |
---|
310 | else |
---|
311 | r = EVP_DigestSignInit(mctx, &pctx, md, impl, sigkey); |
---|
312 | if (!r) { |
---|
313 | BIO_printf(bio_err, "Error setting context\n"); |
---|
314 | ERR_print_errors(bio_err); |
---|
315 | goto end; |
---|
316 | } |
---|
317 | if (sigopts != NULL) { |
---|
318 | char *sigopt; |
---|
319 | for (i = 0; i < sk_OPENSSL_STRING_num(sigopts); i++) { |
---|
320 | sigopt = sk_OPENSSL_STRING_value(sigopts, i); |
---|
321 | if (pkey_ctrl_string(pctx, sigopt) <= 0) { |
---|
322 | BIO_printf(bio_err, "parameter error \"%s\"\n", sigopt); |
---|
323 | ERR_print_errors(bio_err); |
---|
324 | goto end; |
---|
325 | } |
---|
326 | } |
---|
327 | } |
---|
328 | } |
---|
329 | /* we use md as a filter, reading from 'in' */ |
---|
330 | else { |
---|
331 | EVP_MD_CTX *mctx = NULL; |
---|
332 | if (!BIO_get_md_ctx(bmd, &mctx)) { |
---|
333 | BIO_printf(bio_err, "Error getting context\n"); |
---|
334 | ERR_print_errors(bio_err); |
---|
335 | goto end; |
---|
336 | } |
---|
337 | if (md == NULL) |
---|
338 | md = EVP_sha256(); |
---|
339 | if (!EVP_DigestInit_ex(mctx, md, impl)) { |
---|
340 | BIO_printf(bio_err, "Error setting digest\n"); |
---|
341 | ERR_print_errors(bio_err); |
---|
342 | goto end; |
---|
343 | } |
---|
344 | } |
---|
345 | |
---|
346 | if (sigfile != NULL && sigkey != NULL) { |
---|
347 | BIO *sigbio = BIO_new_file(sigfile, "rb"); |
---|
348 | if (sigbio == NULL) { |
---|
349 | BIO_printf(bio_err, "Error opening signature file %s\n", sigfile); |
---|
350 | ERR_print_errors(bio_err); |
---|
351 | goto end; |
---|
352 | } |
---|
353 | siglen = EVP_PKEY_size(sigkey); |
---|
354 | sigbuf = app_malloc(siglen, "signature buffer"); |
---|
355 | siglen = BIO_read(sigbio, sigbuf, siglen); |
---|
356 | BIO_free(sigbio); |
---|
357 | if (siglen <= 0) { |
---|
358 | BIO_printf(bio_err, "Error reading signature file %s\n", sigfile); |
---|
359 | ERR_print_errors(bio_err); |
---|
360 | goto end; |
---|
361 | } |
---|
362 | } |
---|
363 | inp = BIO_push(bmd, in); |
---|
364 | |
---|
365 | if (md == NULL) { |
---|
366 | EVP_MD_CTX *tctx; |
---|
367 | BIO_get_md_ctx(bmd, &tctx); |
---|
368 | md = EVP_MD_CTX_md(tctx); |
---|
369 | } |
---|
370 | |
---|
371 | if (argc == 0) { |
---|
372 | BIO_set_fp(in, stdin, BIO_NOCLOSE); |
---|
373 | ret = do_fp(out, buf, inp, separator, out_bin, sigkey, sigbuf, |
---|
374 | siglen, NULL, NULL, "stdin"); |
---|
375 | } else { |
---|
376 | const char *md_name = NULL, *sig_name = NULL; |
---|
377 | if (!out_bin) { |
---|
378 | if (sigkey != NULL) { |
---|
379 | const EVP_PKEY_ASN1_METHOD *ameth; |
---|
380 | ameth = EVP_PKEY_get0_asn1(sigkey); |
---|
381 | if (ameth) |
---|
382 | EVP_PKEY_asn1_get0_info(NULL, NULL, |
---|
383 | NULL, NULL, &sig_name, ameth); |
---|
384 | } |
---|
385 | if (md != NULL) |
---|
386 | md_name = EVP_MD_name(md); |
---|
387 | } |
---|
388 | ret = 0; |
---|
389 | for (i = 0; i < argc; i++) { |
---|
390 | int r; |
---|
391 | if (BIO_read_filename(in, argv[i]) <= 0) { |
---|
392 | perror(argv[i]); |
---|
393 | ret++; |
---|
394 | continue; |
---|
395 | } else { |
---|
396 | r = do_fp(out, buf, inp, separator, out_bin, sigkey, sigbuf, |
---|
397 | siglen, sig_name, md_name, argv[i]); |
---|
398 | } |
---|
399 | if (r) |
---|
400 | ret = r; |
---|
401 | (void)BIO_reset(bmd); |
---|
402 | } |
---|
403 | } |
---|
404 | end: |
---|
405 | OPENSSL_clear_free(buf, BUFSIZE); |
---|
406 | BIO_free(in); |
---|
407 | OPENSSL_free(passin); |
---|
408 | BIO_free_all(out); |
---|
409 | EVP_PKEY_free(sigkey); |
---|
410 | sk_OPENSSL_STRING_free(sigopts); |
---|
411 | sk_OPENSSL_STRING_free(macopts); |
---|
412 | OPENSSL_free(sigbuf); |
---|
413 | BIO_free(bmd); |
---|
414 | release_engine(e); |
---|
415 | return ret; |
---|
416 | } |
---|
417 | |
---|
418 | int do_fp(BIO *out, unsigned char *buf, BIO *bp, int sep, int binout, |
---|
419 | EVP_PKEY *key, unsigned char *sigin, int siglen, |
---|
420 | const char *sig_name, const char *md_name, |
---|
421 | const char *file) |
---|
422 | { |
---|
423 | size_t len; |
---|
424 | int i; |
---|
425 | |
---|
426 | for (;;) { |
---|
427 | i = BIO_read(bp, (char *)buf, BUFSIZE); |
---|
428 | if (i < 0) { |
---|
429 | BIO_printf(bio_err, "Read Error in %s\n", file); |
---|
430 | ERR_print_errors(bio_err); |
---|
431 | return 1; |
---|
432 | } |
---|
433 | if (i == 0) |
---|
434 | break; |
---|
435 | } |
---|
436 | if (sigin != NULL) { |
---|
437 | EVP_MD_CTX *ctx; |
---|
438 | BIO_get_md_ctx(bp, &ctx); |
---|
439 | i = EVP_DigestVerifyFinal(ctx, sigin, (unsigned int)siglen); |
---|
440 | if (i > 0) { |
---|
441 | BIO_printf(out, "Verified OK\n"); |
---|
442 | } else if (i == 0) { |
---|
443 | BIO_printf(out, "Verification Failure\n"); |
---|
444 | return 1; |
---|
445 | } else { |
---|
446 | BIO_printf(bio_err, "Error Verifying Data\n"); |
---|
447 | ERR_print_errors(bio_err); |
---|
448 | return 1; |
---|
449 | } |
---|
450 | return 0; |
---|
451 | } |
---|
452 | if (key != NULL) { |
---|
453 | EVP_MD_CTX *ctx; |
---|
454 | BIO_get_md_ctx(bp, &ctx); |
---|
455 | len = BUFSIZE; |
---|
456 | if (!EVP_DigestSignFinal(ctx, buf, &len)) { |
---|
457 | BIO_printf(bio_err, "Error Signing Data\n"); |
---|
458 | ERR_print_errors(bio_err); |
---|
459 | return 1; |
---|
460 | } |
---|
461 | } else { |
---|
462 | len = BIO_gets(bp, (char *)buf, BUFSIZE); |
---|
463 | if ((int)len < 0) { |
---|
464 | ERR_print_errors(bio_err); |
---|
465 | return 1; |
---|
466 | } |
---|
467 | } |
---|
468 | |
---|
469 | if (binout) { |
---|
470 | BIO_write(out, buf, len); |
---|
471 | } else if (sep == 2) { |
---|
472 | for (i = 0; i < (int)len; i++) |
---|
473 | BIO_printf(out, "%02x", buf[i]); |
---|
474 | BIO_printf(out, " *%s\n", file); |
---|
475 | } else { |
---|
476 | if (sig_name != NULL) { |
---|
477 | BIO_puts(out, sig_name); |
---|
478 | if (md_name != NULL) |
---|
479 | BIO_printf(out, "-%s", md_name); |
---|
480 | BIO_printf(out, "(%s)= ", file); |
---|
481 | } else if (md_name != NULL) { |
---|
482 | BIO_printf(out, "%s(%s)= ", md_name, file); |
---|
483 | } else { |
---|
484 | BIO_printf(out, "(%s)= ", file); |
---|
485 | } |
---|
486 | for (i = 0; i < (int)len; i++) { |
---|
487 | if (sep && (i != 0)) |
---|
488 | BIO_printf(out, ":"); |
---|
489 | BIO_printf(out, "%02x", buf[i]); |
---|
490 | } |
---|
491 | BIO_printf(out, "\n"); |
---|
492 | } |
---|
493 | return 0; |
---|
494 | } |
---|