]>
Commit | Line | Data |
---|---|---|
0f113f3e | 1 | /* |
454afd98 | 2 | * Copyright 2008-2020 The OpenSSL Project Authors. All Rights Reserved. |
f4cc56f4 | 3 | * |
08ddd302 | 4 | * Licensed under the Apache License 2.0 (the "License"). You may not use |
b1322259 RS |
5 | * this file except in compliance with the License. You can obtain a copy |
6 | * in the file LICENSE in the source distribution or at | |
7 | * https://www.openssl.org/source/license.html | |
f4cc56f4 DSH |
8 | */ |
9 | ||
b39fc560 | 10 | #include "internal/cryptlib.h" |
f4cc56f4 DSH |
11 | #include <openssl/asn1t.h> |
12 | #include <openssl/pem.h> | |
13 | #include <openssl/rand.h> | |
14 | #include <openssl/x509v3.h> | |
15 | #include <openssl/err.h> | |
16 | #include <openssl/cms.h> | |
e85d19c6 | 17 | #include <openssl/ess.h> |
706457b7 | 18 | #include "cms_local.h" |
25f2138b DMSP |
19 | #include "crypto/ess.h" |
20 | #include "crypto/cms.h" | |
f4cc56f4 | 21 | |
852c2ed2 RS |
22 | DEFINE_STACK_OF(GENERAL_NAMES) |
23 | DEFINE_STACK_OF(CMS_SignerInfo) | |
9e3c510b F |
24 | DEFINE_STACK_OF(ESS_CERT_ID) |
25 | DEFINE_STACK_OF(ESS_CERT_ID_V2) | |
26 | DEFINE_STACK_OF(X509) | |
852c2ed2 | 27 | |
f4cc56f4 DSH |
28 | IMPLEMENT_ASN1_FUNCTIONS(CMS_ReceiptRequest) |
29 | ||
e85d19c6 | 30 | /* ESS services */ |
f4cc56f4 DSH |
31 | |
32 | int CMS_get1_ReceiptRequest(CMS_SignerInfo *si, CMS_ReceiptRequest **prr) | |
0f113f3e MC |
33 | { |
34 | ASN1_STRING *str; | |
9e3c510b F |
35 | CMS_ReceiptRequest *rr; |
36 | ASN1_OBJECT *obj = OBJ_nid2obj(NID_id_smime_aa_receiptRequest); | |
37 | ||
38 | if (prr != NULL) | |
0f113f3e | 39 | *prr = NULL; |
9e3c510b F |
40 | str = CMS_signed_get0_data_by_OBJ(si, obj, -3, V_ASN1_SEQUENCE); |
41 | if (str == NULL) | |
0f113f3e MC |
42 | return 0; |
43 | ||
44 | rr = ASN1_item_unpack(str, ASN1_ITEM_rptr(CMS_ReceiptRequest)); | |
9e3c510b | 45 | if (rr == NULL) |
0f113f3e | 46 | return -1; |
9e3c510b | 47 | if (prr != NULL) |
0f113f3e MC |
48 | *prr = rr; |
49 | else | |
50 | CMS_ReceiptRequest_free(rr); | |
51 | return 1; | |
52 | } | |
f4cc56f4 | 53 | |
9e3c510b F |
54 | /* |
55 | First, get the ESS_SIGNING_CERT(V2) signed attribute from |si|. | |
56 | Then check matching of each cert of trust |chain| with one of | |
57 | the |cert_ids|(Hash+IssuerID) list from this ESS_SIGNING_CERT. | |
58 | Derived from ts_check_signing_certs() | |
59 | */ | |
60 | int ess_check_signing_certs(CMS_SignerInfo *si, STACK_OF(X509) *chain) | |
61 | { | |
62 | ESS_SIGNING_CERT *ss = NULL; | |
63 | ESS_SIGNING_CERT_V2 *ssv2 = NULL; | |
64 | X509 *cert; | |
65 | int i = 0, ret = 0; | |
66 | ||
67 | if (cms_signerinfo_get_signing_cert(si, &ss) > 0 && ss->cert_ids != NULL) { | |
68 | STACK_OF(ESS_CERT_ID) *cert_ids = ss->cert_ids; | |
69 | ||
70 | cert = sk_X509_value(chain, 0); | |
71 | if (ess_find_cert(cert_ids, cert) != 0) | |
72 | goto err; | |
73 | ||
74 | /* | |
75 | * Check the other certificates of the chain. | |
76 | * Fail if no signing certificate ids found for each certificate. | |
77 | */ | |
78 | if (sk_ESS_CERT_ID_num(cert_ids) > 1) { | |
79 | /* for each chain cert, try to find its cert id */ | |
80 | for (i = 1; i < sk_X509_num(chain); ++i) { | |
81 | cert = sk_X509_value(chain, i); | |
82 | if (ess_find_cert(cert_ids, cert) < 0) | |
83 | goto err; | |
84 | } | |
85 | } | |
86 | } else if (cms_signerinfo_get_signing_cert_v2(si, &ssv2) > 0 | |
87 | && ssv2->cert_ids!= NULL) { | |
88 | STACK_OF(ESS_CERT_ID_V2) *cert_ids_v2 = ssv2->cert_ids; | |
89 | ||
90 | cert = sk_X509_value(chain, 0); | |
91 | if (ess_find_cert_v2(cert_ids_v2, cert) != 0) | |
92 | goto err; | |
93 | ||
94 | /* | |
95 | * Check the other certificates of the chain. | |
96 | * Fail if no signing certificate ids found for each certificate. | |
97 | */ | |
98 | if (sk_ESS_CERT_ID_V2_num(cert_ids_v2) > 1) { | |
99 | /* for each chain cert, try to find its cert id */ | |
100 | for (i = 1; i < sk_X509_num(chain); ++i) { | |
101 | cert = sk_X509_value(chain, i); | |
102 | if (ess_find_cert_v2(cert_ids_v2, cert) < 0) | |
103 | goto err; | |
104 | } | |
105 | } | |
106 | } else { | |
107 | CMSerr(CMS_F_ESS_CHECK_SIGNING_CERTS, | |
108 | CMS_R_ESS_NO_SIGNING_CERTID_ATTRIBUTE); | |
109 | return 0; | |
110 | } | |
111 | ret = 1; | |
112 | err: | |
113 | if (!ret) | |
114 | CMSerr(CMS_F_ESS_CHECK_SIGNING_CERTS, | |
115 | CMS_R_ESS_SIGNING_CERTID_MISMATCH_ERROR); | |
116 | ||
117 | ESS_SIGNING_CERT_free(ss); | |
118 | ESS_SIGNING_CERT_V2_free(ssv2); | |
119 | return ret; | |
120 | } | |
121 | ||
f5e2354c | 122 | CMS_ReceiptRequest *CMS_ReceiptRequest_create0(unsigned char *id, int idlen, |
0f113f3e MC |
123 | int allorfirst, |
124 | STACK_OF(GENERAL_NAMES) | |
125 | *receiptList, STACK_OF(GENERAL_NAMES) | |
126 | *receiptsTo) | |
127 | { | |
9e3c510b | 128 | CMS_ReceiptRequest *rr; |
0f113f3e MC |
129 | |
130 | rr = CMS_ReceiptRequest_new(); | |
90945fa3 | 131 | if (rr == NULL) |
0f113f3e MC |
132 | goto merr; |
133 | if (id) | |
134 | ASN1_STRING_set0(rr->signedContentIdentifier, id, idlen); | |
135 | else { | |
136 | if (!ASN1_STRING_set(rr->signedContentIdentifier, NULL, 32)) | |
137 | goto merr; | |
266483d2 | 138 | if (RAND_bytes(rr->signedContentIdentifier->data, 32) <= 0) |
0f113f3e MC |
139 | goto err; |
140 | } | |
141 | ||
142 | sk_GENERAL_NAMES_pop_free(rr->receiptsTo, GENERAL_NAMES_free); | |
143 | rr->receiptsTo = receiptsTo; | |
144 | ||
145 | if (receiptList) { | |
146 | rr->receiptsFrom->type = 1; | |
147 | rr->receiptsFrom->d.receiptList = receiptList; | |
148 | } else { | |
149 | rr->receiptsFrom->type = 0; | |
150 | rr->receiptsFrom->d.allOrFirstTier = allorfirst; | |
151 | } | |
152 | ||
153 | return rr; | |
154 | ||
155 | merr: | |
156 | CMSerr(CMS_F_CMS_RECEIPTREQUEST_CREATE0, ERR_R_MALLOC_FAILURE); | |
157 | ||
158 | err: | |
25aaa98a | 159 | CMS_ReceiptRequest_free(rr); |
0f113f3e MC |
160 | return NULL; |
161 | ||
162 | } | |
f5e2354c DSH |
163 | |
164 | int CMS_add1_ReceiptRequest(CMS_SignerInfo *si, CMS_ReceiptRequest *rr) | |
0f113f3e MC |
165 | { |
166 | unsigned char *rrder = NULL; | |
167 | int rrderlen, r = 0; | |
f5e2354c | 168 | |
0f113f3e MC |
169 | rrderlen = i2d_CMS_ReceiptRequest(rr, &rrder); |
170 | if (rrderlen < 0) | |
171 | goto merr; | |
f5e2354c | 172 | |
0f113f3e MC |
173 | if (!CMS_signed_add1_attr_by_NID(si, NID_id_smime_aa_receiptRequest, |
174 | V_ASN1_SEQUENCE, rrder, rrderlen)) | |
175 | goto merr; | |
f5e2354c | 176 | |
0f113f3e | 177 | r = 1; |
f5e2354c | 178 | |
0f113f3e MC |
179 | merr: |
180 | if (!r) | |
181 | CMSerr(CMS_F_CMS_ADD1_RECEIPTREQUEST, ERR_R_MALLOC_FAILURE); | |
f5e2354c | 182 | |
b548a1f1 | 183 | OPENSSL_free(rrder); |
f4cc56f4 | 184 | |
0f113f3e MC |
185 | return r; |
186 | ||
187 | } | |
f4cc56f4 DSH |
188 | |
189 | void CMS_ReceiptRequest_get0_values(CMS_ReceiptRequest *rr, | |
0f113f3e MC |
190 | ASN1_STRING **pcid, |
191 | int *pallorfirst, | |
192 | STACK_OF(GENERAL_NAMES) **plist, | |
193 | STACK_OF(GENERAL_NAMES) **prto) | |
194 | { | |
195 | if (pcid) | |
196 | *pcid = rr->signedContentIdentifier; | |
197 | if (rr->receiptsFrom->type == 0) { | |
198 | if (pallorfirst) | |
199 | *pallorfirst = (int)rr->receiptsFrom->d.allOrFirstTier; | |
200 | if (plist) | |
201 | *plist = NULL; | |
202 | } else { | |
203 | if (pallorfirst) | |
204 | *pallorfirst = -1; | |
205 | if (plist) | |
206 | *plist = rr->receiptsFrom->d.receiptList; | |
207 | } | |
208 | if (prto) | |
209 | *prto = rr->receiptsTo; | |
210 | } | |
f4cc56f4 | 211 | |
36309aa2 DSH |
212 | /* Digest a SignerInfo structure for msgSigDigest attribute processing */ |
213 | ||
eb9d8d8c | 214 | static int cms_msgSigDigest(CMS_SignerInfo *si, |
0f113f3e MC |
215 | unsigned char *dig, unsigned int *diglen) |
216 | { | |
217 | const EVP_MD *md; | |
9e3c510b | 218 | |
0f113f3e MC |
219 | md = EVP_get_digestbyobj(si->digestAlgorithm->algorithm); |
220 | if (md == NULL) | |
221 | return 0; | |
222 | if (!ASN1_item_digest(ASN1_ITEM_rptr(CMS_Attributes_Verify), md, | |
223 | si->signedAttrs, dig, diglen)) | |
224 | return 0; | |
225 | return 1; | |
226 | } | |
eb9d8d8c | 227 | |
36309aa2 DSH |
228 | /* Add a msgSigDigest attribute to a SignerInfo */ |
229 | ||
230 | int cms_msgSigDigest_add1(CMS_SignerInfo *dest, CMS_SignerInfo *src) | |
0f113f3e MC |
231 | { |
232 | unsigned char dig[EVP_MAX_MD_SIZE]; | |
233 | unsigned int diglen; | |
9e3c510b | 234 | |
0f113f3e MC |
235 | if (!cms_msgSigDigest(src, dig, &diglen)) { |
236 | CMSerr(CMS_F_CMS_MSGSIGDIGEST_ADD1, CMS_R_MSGSIGDIGEST_ERROR); | |
237 | return 0; | |
238 | } | |
239 | if (!CMS_signed_add1_attr_by_NID(dest, NID_id_smime_aa_msgSigDigest, | |
240 | V_ASN1_OCTET_STRING, dig, diglen)) { | |
241 | CMSerr(CMS_F_CMS_MSGSIGDIGEST_ADD1, ERR_R_MALLOC_FAILURE); | |
242 | return 0; | |
243 | } | |
244 | return 1; | |
245 | } | |
36309aa2 | 246 | |
eb9d8d8c DSH |
247 | /* Verify signed receipt after it has already passed normal CMS verify */ |
248 | ||
249 | int cms_Receipt_verify(CMS_ContentInfo *cms, CMS_ContentInfo *req_cms) | |
0f113f3e MC |
250 | { |
251 | int r = 0, i; | |
252 | CMS_ReceiptRequest *rr = NULL; | |
253 | CMS_Receipt *rct = NULL; | |
254 | STACK_OF(CMS_SignerInfo) *sis, *osis; | |
255 | CMS_SignerInfo *si, *osi = NULL; | |
256 | ASN1_OCTET_STRING *msig, **pcont; | |
257 | ASN1_OBJECT *octype; | |
258 | unsigned char dig[EVP_MAX_MD_SIZE]; | |
259 | unsigned int diglen; | |
260 | ||
261 | /* Get SignerInfos, also checks SignedData content type */ | |
262 | osis = CMS_get0_SignerInfos(req_cms); | |
263 | sis = CMS_get0_SignerInfos(cms); | |
264 | if (!osis || !sis) | |
265 | goto err; | |
266 | ||
267 | if (sk_CMS_SignerInfo_num(sis) != 1) { | |
268 | CMSerr(CMS_F_CMS_RECEIPT_VERIFY, CMS_R_NEED_ONE_SIGNER); | |
269 | goto err; | |
270 | } | |
271 | ||
272 | /* Check receipt content type */ | |
273 | if (OBJ_obj2nid(CMS_get0_eContentType(cms)) != NID_id_smime_ct_receipt) { | |
274 | CMSerr(CMS_F_CMS_RECEIPT_VERIFY, CMS_R_NOT_A_SIGNED_RECEIPT); | |
275 | goto err; | |
276 | } | |
277 | ||
278 | /* Extract and decode receipt content */ | |
279 | pcont = CMS_get0_content(cms); | |
12a765a5 | 280 | if (pcont == NULL || *pcont == NULL) { |
0f113f3e MC |
281 | CMSerr(CMS_F_CMS_RECEIPT_VERIFY, CMS_R_NO_CONTENT); |
282 | goto err; | |
283 | } | |
284 | ||
285 | rct = ASN1_item_unpack(*pcont, ASN1_ITEM_rptr(CMS_Receipt)); | |
286 | ||
287 | if (!rct) { | |
288 | CMSerr(CMS_F_CMS_RECEIPT_VERIFY, CMS_R_RECEIPT_DECODE_ERROR); | |
289 | goto err; | |
290 | } | |
291 | ||
292 | /* Locate original request */ | |
293 | ||
294 | for (i = 0; i < sk_CMS_SignerInfo_num(osis); i++) { | |
295 | osi = sk_CMS_SignerInfo_value(osis, i); | |
296 | if (!ASN1_STRING_cmp(osi->signature, rct->originatorSignatureValue)) | |
297 | break; | |
298 | } | |
299 | ||
300 | if (i == sk_CMS_SignerInfo_num(osis)) { | |
301 | CMSerr(CMS_F_CMS_RECEIPT_VERIFY, CMS_R_NO_MATCHING_SIGNATURE); | |
302 | goto err; | |
303 | } | |
304 | ||
305 | si = sk_CMS_SignerInfo_value(sis, 0); | |
306 | ||
307 | /* Get msgSigDigest value and compare */ | |
308 | ||
309 | msig = CMS_signed_get0_data_by_OBJ(si, | |
310 | OBJ_nid2obj | |
311 | (NID_id_smime_aa_msgSigDigest), -3, | |
312 | V_ASN1_OCTET_STRING); | |
313 | ||
314 | if (!msig) { | |
315 | CMSerr(CMS_F_CMS_RECEIPT_VERIFY, CMS_R_NO_MSGSIGDIGEST); | |
316 | goto err; | |
317 | } | |
318 | ||
319 | if (!cms_msgSigDigest(osi, dig, &diglen)) { | |
320 | CMSerr(CMS_F_CMS_RECEIPT_VERIFY, CMS_R_MSGSIGDIGEST_ERROR); | |
321 | goto err; | |
322 | } | |
323 | ||
324 | if (diglen != (unsigned int)msig->length) { | |
325 | CMSerr(CMS_F_CMS_RECEIPT_VERIFY, CMS_R_MSGSIGDIGEST_WRONG_LENGTH); | |
326 | goto err; | |
327 | } | |
328 | ||
329 | if (memcmp(dig, msig->data, diglen)) { | |
330 | CMSerr(CMS_F_CMS_RECEIPT_VERIFY, | |
331 | CMS_R_MSGSIGDIGEST_VERIFICATION_FAILURE); | |
332 | goto err; | |
333 | } | |
334 | ||
335 | /* Compare content types */ | |
336 | ||
337 | octype = CMS_signed_get0_data_by_OBJ(osi, | |
338 | OBJ_nid2obj(NID_pkcs9_contentType), | |
339 | -3, V_ASN1_OBJECT); | |
340 | if (!octype) { | |
341 | CMSerr(CMS_F_CMS_RECEIPT_VERIFY, CMS_R_NO_CONTENT_TYPE); | |
342 | goto err; | |
343 | } | |
344 | ||
345 | /* Compare details in receipt request */ | |
346 | ||
347 | if (OBJ_cmp(octype, rct->contentType)) { | |
348 | CMSerr(CMS_F_CMS_RECEIPT_VERIFY, CMS_R_CONTENT_TYPE_MISMATCH); | |
349 | goto err; | |
350 | } | |
351 | ||
352 | /* Get original receipt request details */ | |
353 | ||
354 | if (CMS_get1_ReceiptRequest(osi, &rr) <= 0) { | |
355 | CMSerr(CMS_F_CMS_RECEIPT_VERIFY, CMS_R_NO_RECEIPT_REQUEST); | |
356 | goto err; | |
357 | } | |
358 | ||
359 | if (ASN1_STRING_cmp(rr->signedContentIdentifier, | |
360 | rct->signedContentIdentifier)) { | |
361 | CMSerr(CMS_F_CMS_RECEIPT_VERIFY, CMS_R_CONTENTIDENTIFIER_MISMATCH); | |
362 | goto err; | |
363 | } | |
364 | ||
365 | r = 1; | |
366 | ||
367 | err: | |
25aaa98a | 368 | CMS_ReceiptRequest_free(rr); |
2ace7450 | 369 | M_ASN1_free_of(rct, CMS_Receipt); |
0f113f3e MC |
370 | return r; |
371 | ||
372 | } | |
373 | ||
374 | /* | |
375 | * Encode a Receipt into an OCTET STRING read for including into content of a | |
376 | * SignedData ContentInfo. | |
36309aa2 DSH |
377 | */ |
378 | ||
379 | ASN1_OCTET_STRING *cms_encode_Receipt(CMS_SignerInfo *si) | |
0f113f3e MC |
380 | { |
381 | CMS_Receipt rct; | |
382 | CMS_ReceiptRequest *rr = NULL; | |
383 | ASN1_OBJECT *ctype; | |
384 | ASN1_OCTET_STRING *os = NULL; | |
36309aa2 | 385 | |
0f113f3e | 386 | /* Get original receipt request */ |
36309aa2 | 387 | |
0f113f3e | 388 | /* Get original receipt request details */ |
36309aa2 | 389 | |
0f113f3e MC |
390 | if (CMS_get1_ReceiptRequest(si, &rr) <= 0) { |
391 | CMSerr(CMS_F_CMS_ENCODE_RECEIPT, CMS_R_NO_RECEIPT_REQUEST); | |
392 | goto err; | |
393 | } | |
36309aa2 | 394 | |
0f113f3e | 395 | /* Get original content type */ |
36309aa2 | 396 | |
0f113f3e MC |
397 | ctype = CMS_signed_get0_data_by_OBJ(si, |
398 | OBJ_nid2obj(NID_pkcs9_contentType), | |
399 | -3, V_ASN1_OBJECT); | |
400 | if (!ctype) { | |
401 | CMSerr(CMS_F_CMS_ENCODE_RECEIPT, CMS_R_NO_CONTENT_TYPE); | |
402 | goto err; | |
403 | } | |
36309aa2 | 404 | |
0f113f3e MC |
405 | rct.version = 1; |
406 | rct.contentType = ctype; | |
407 | rct.signedContentIdentifier = rr->signedContentIdentifier; | |
408 | rct.originatorSignatureValue = si->signature; | |
36309aa2 | 409 | |
0f113f3e | 410 | os = ASN1_item_pack(&rct, ASN1_ITEM_rptr(CMS_Receipt), NULL); |
36309aa2 | 411 | |
0f113f3e | 412 | err: |
25aaa98a | 413 | CMS_ReceiptRequest_free(rr); |
0f113f3e | 414 | return os; |
0f113f3e | 415 | } |
e85d19c6 AI |
416 | |
417 | /* | |
8c00f267 | 418 | * Add signer certificate's V2 digest |sc| to a SignerInfo structure |si| |
e85d19c6 AI |
419 | */ |
420 | ||
8c00f267 | 421 | int cms_add1_signing_cert_v2(CMS_SignerInfo *si, ESS_SIGNING_CERT_V2 *sc) |
e85d19c6 AI |
422 | { |
423 | ASN1_STRING *seq = NULL; | |
424 | unsigned char *p, *pp; | |
425 | int len; | |
426 | ||
427 | /* Add SigningCertificateV2 signed attribute to the signer info. */ | |
428 | len = i2d_ESS_SIGNING_CERT_V2(sc, NULL); | |
429 | if ((pp = OPENSSL_malloc(len)) == NULL) | |
430 | goto err; | |
431 | p = pp; | |
432 | i2d_ESS_SIGNING_CERT_V2(sc, &p); | |
433 | if (!(seq = ASN1_STRING_new()) || !ASN1_STRING_set(seq, pp, len)) | |
434 | goto err; | |
435 | OPENSSL_free(pp); | |
436 | pp = NULL; | |
437 | if (!CMS_signed_add1_attr_by_NID(si, NID_id_smime_aa_signingCertificateV2, | |
438 | V_ASN1_SEQUENCE, seq, -1)) | |
439 | goto err; | |
440 | ASN1_STRING_free(seq); | |
441 | return 1; | |
442 | err: | |
443 | CMSerr(CMS_F_CMS_ADD1_SIGNING_CERT_V2, ERR_R_MALLOC_FAILURE); | |
444 | ASN1_STRING_free(seq); | |
445 | OPENSSL_free(pp); | |
446 | return 0; | |
447 | } | |
448 | ||
449 | /* | |
8c00f267 | 450 | * Add signer certificate's digest |sc| to a SignerInfo structure |si| |
e85d19c6 AI |
451 | */ |
452 | ||
8c00f267 | 453 | int cms_add1_signing_cert(CMS_SignerInfo *si, ESS_SIGNING_CERT *sc) |
e85d19c6 AI |
454 | { |
455 | ASN1_STRING *seq = NULL; | |
456 | unsigned char *p, *pp; | |
457 | int len; | |
458 | ||
459 | /* Add SigningCertificate signed attribute to the signer info. */ | |
460 | len = i2d_ESS_SIGNING_CERT(sc, NULL); | |
461 | if ((pp = OPENSSL_malloc(len)) == NULL) | |
462 | goto err; | |
463 | p = pp; | |
464 | i2d_ESS_SIGNING_CERT(sc, &p); | |
465 | if (!(seq = ASN1_STRING_new()) || !ASN1_STRING_set(seq, pp, len)) | |
466 | goto err; | |
467 | OPENSSL_free(pp); | |
468 | pp = NULL; | |
469 | if (!CMS_signed_add1_attr_by_NID(si, NID_id_smime_aa_signingCertificate, | |
470 | V_ASN1_SEQUENCE, seq, -1)) | |
471 | goto err; | |
472 | ASN1_STRING_free(seq); | |
473 | return 1; | |
474 | err: | |
475 | CMSerr(CMS_F_CMS_ADD1_SIGNING_CERT, ERR_R_MALLOC_FAILURE); | |
476 | ASN1_STRING_free(seq); | |
477 | OPENSSL_free(pp); | |
478 | return 0; | |
479 | } |