xref: /openbsd/usr.sbin/rpki-client/mft.c (revision c7a965b3)
1 /*	$OpenBSD: mft.c,v 1.116 2024/05/24 12:57:20 tb Exp $ */
2 /*
3  * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
4  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <assert.h>
20 #include <err.h>
21 #include <limits.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 
27 #include <openssl/bn.h>
28 #include <openssl/asn1.h>
29 #include <openssl/asn1t.h>
30 #include <openssl/safestack.h>
31 #include <openssl/sha.h>
32 #include <openssl/stack.h>
33 #include <openssl/x509.h>
34 
35 #include "extern.h"
36 
37 extern ASN1_OBJECT	*mft_oid;
38 
39 /*
40  * Types and templates for the Manifest eContent, RFC 6486, section 4.2.
41  */
42 
43 ASN1_ITEM_EXP FileAndHash_it;
44 ASN1_ITEM_EXP Manifest_it;
45 
46 typedef struct {
47 	ASN1_IA5STRING	*file;
48 	ASN1_BIT_STRING	*hash;
49 } FileAndHash;
50 
51 DECLARE_STACK_OF(FileAndHash);
52 
53 #ifndef DEFINE_STACK_OF
54 #define sk_FileAndHash_dup(sk)		SKM_sk_dup(FileAndHash, (sk))
55 #define sk_FileAndHash_free(sk)		SKM_sk_free(FileAndHash, (sk))
56 #define sk_FileAndHash_num(sk)		SKM_sk_num(FileAndHash, (sk))
57 #define sk_FileAndHash_value(sk, i)	SKM_sk_value(FileAndHash, (sk), (i))
58 #define sk_FileAndHash_sort(sk)		SKM_sk_sort(FileAndHash, (sk))
59 #define sk_FileAndHash_set_cmp_func(sk, cmp) \
60     SKM_sk_set_cmp_func(FileAndHash, (sk), (cmp))
61 #endif
62 
63 typedef struct {
64 	ASN1_INTEGER		*version;
65 	ASN1_INTEGER		*manifestNumber;
66 	ASN1_GENERALIZEDTIME	*thisUpdate;
67 	ASN1_GENERALIZEDTIME	*nextUpdate;
68 	ASN1_OBJECT		*fileHashAlg;
69 	STACK_OF(FileAndHash)	*fileList;
70 } Manifest;
71 
72 ASN1_SEQUENCE(FileAndHash) = {
73 	ASN1_SIMPLE(FileAndHash, file, ASN1_IA5STRING),
74 	ASN1_SIMPLE(FileAndHash, hash, ASN1_BIT_STRING),
75 } ASN1_SEQUENCE_END(FileAndHash);
76 
77 ASN1_SEQUENCE(Manifest) = {
78 	ASN1_EXP_OPT(Manifest, version, ASN1_INTEGER, 0),
79 	ASN1_SIMPLE(Manifest, manifestNumber, ASN1_INTEGER),
80 	ASN1_SIMPLE(Manifest, thisUpdate, ASN1_GENERALIZEDTIME),
81 	ASN1_SIMPLE(Manifest, nextUpdate, ASN1_GENERALIZEDTIME),
82 	ASN1_SIMPLE(Manifest, fileHashAlg, ASN1_OBJECT),
83 	ASN1_SEQUENCE_OF(Manifest, fileList, FileAndHash),
84 } ASN1_SEQUENCE_END(Manifest);
85 
86 DECLARE_ASN1_FUNCTIONS(Manifest);
87 IMPLEMENT_ASN1_FUNCTIONS(Manifest);
88 
89 #define GENTIME_LENGTH 15
90 
91 /*
92  * Determine rtype corresponding to file extension. Returns RTYPE_INVALID
93  * on error or unkown extension.
94  */
95 enum rtype
rtype_from_file_extension(const char * fn)96 rtype_from_file_extension(const char *fn)
97 {
98 	size_t	 sz;
99 
100 	sz = strlen(fn);
101 	if (sz < 5)
102 		return RTYPE_INVALID;
103 
104 	if (strcasecmp(fn + sz - 4, ".tal") == 0)
105 		return RTYPE_TAL;
106 	if (strcasecmp(fn + sz - 4, ".cer") == 0)
107 		return RTYPE_CER;
108 	if (strcasecmp(fn + sz - 4, ".crl") == 0)
109 		return RTYPE_CRL;
110 	if (strcasecmp(fn + sz - 4, ".mft") == 0)
111 		return RTYPE_MFT;
112 	if (strcasecmp(fn + sz - 4, ".roa") == 0)
113 		return RTYPE_ROA;
114 	if (strcasecmp(fn + sz - 4, ".gbr") == 0)
115 		return RTYPE_GBR;
116 	if (strcasecmp(fn + sz - 4, ".sig") == 0)
117 		return RTYPE_RSC;
118 	if (strcasecmp(fn + sz - 4, ".asa") == 0)
119 		return RTYPE_ASPA;
120 	if (strcasecmp(fn + sz - 4, ".tak") == 0)
121 		return RTYPE_TAK;
122 	if (strcasecmp(fn + sz - 4, ".csv") == 0)
123 		return RTYPE_GEOFEED;
124 	if (strcasecmp(fn + sz - 4, ".spl") == 0)
125 		return RTYPE_SPL;
126 
127 	return RTYPE_INVALID;
128 }
129 
130 /*
131  * Validate that a filename listed on a Manifest only contains characters
132  * permitted in RFC 9286 section 4.2.2.
133  * Also ensure that there is exactly one '.'.
134  */
135 static int
valid_mft_filename(const char * fn,size_t len)136 valid_mft_filename(const char *fn, size_t len)
137 {
138 	const unsigned char *c;
139 
140 	if (!valid_filename(fn, len))
141 		return 0;
142 
143 	c = memchr(fn, '.', len);
144 	if (c == NULL || c != memrchr(fn, '.', len))
145 		return 0;
146 
147 	return 1;
148 }
149 
150 /*
151  * Check that the file is allowed to be part of a manifest and the parser
152  * for this type is implemented in rpki-client.
153  * Returns corresponding rtype or RTYPE_INVALID to mark the file as unknown.
154  */
155 static enum rtype
rtype_from_mftfile(const char * fn)156 rtype_from_mftfile(const char *fn)
157 {
158 	enum rtype		 type;
159 
160 	type = rtype_from_file_extension(fn);
161 	switch (type) {
162 	case RTYPE_CER:
163 	case RTYPE_CRL:
164 	case RTYPE_GBR:
165 	case RTYPE_ROA:
166 	case RTYPE_ASPA:
167 	case RTYPE_SPL:
168 	case RTYPE_TAK:
169 		return type;
170 	default:
171 		return RTYPE_INVALID;
172 	}
173 }
174 
175 /*
176  * Parse an individual "FileAndHash", RFC 6486, sec. 4.2.
177  * Return zero on failure, non-zero on success.
178  */
179 static int
mft_parse_filehash(const char * fn,struct mft * mft,const FileAndHash * fh,int * found_crl)180 mft_parse_filehash(const char *fn, struct mft *mft, const FileAndHash *fh,
181     int *found_crl)
182 {
183 	char			*file = NULL;
184 	int			 rc = 0;
185 	struct mftfile		*fent;
186 	enum rtype		 type;
187 	size_t			 new_idx = 0;
188 
189 	if (!valid_mft_filename(fh->file->data, fh->file->length)) {
190 		warnx("%s: RFC 6486 section 4.2.2: bad filename", fn);
191 		goto out;
192 	}
193 	file = strndup(fh->file->data, fh->file->length);
194 	if (file == NULL)
195 		err(1, NULL);
196 
197 	if (fh->hash->length != SHA256_DIGEST_LENGTH) {
198 		warnx("%s: RFC 6486 section 4.2.1: hash: "
199 		    "invalid SHA256 length, have %d", fn, fh->hash->length);
200 		goto out;
201 	}
202 
203 	type = rtype_from_mftfile(file);
204 	if (type == RTYPE_CRL) {
205 		if (*found_crl == 1) {
206 			warnx("%s: RFC 6487: too many CRLs listed on MFT", fn);
207 			goto out;
208 		}
209 		if (strcmp(file, mft->crl) != 0) {
210 			warnx("%s: RFC 6487: name (%s) doesn't match CRLDP "
211 			    "(%s)", fn, file, mft->crl);
212 			goto out;
213 		}
214 		/* remember the filehash for the CRL in struct mft */
215 		memcpy(mft->crlhash, fh->hash->data, SHA256_DIGEST_LENGTH);
216 		*found_crl = 1;
217 	}
218 
219 	if (filemode)
220 		fent = &mft->files[mft->filesz++];
221 	else {
222 		/* Fisher-Yates shuffle */
223 		new_idx = arc4random_uniform(mft->filesz + 1);
224 		mft->files[mft->filesz++] = mft->files[new_idx];
225 		fent = &mft->files[new_idx];
226 	}
227 
228 	fent->type = type;
229 	fent->file = file;
230 	file = NULL;
231 	memcpy(fent->hash, fh->hash->data, SHA256_DIGEST_LENGTH);
232 
233 	rc = 1;
234  out:
235 	free(file);
236 	return rc;
237 }
238 
239 static int
mft_fh_cmp_name(const FileAndHash * const * a,const FileAndHash * const * b)240 mft_fh_cmp_name(const FileAndHash *const *a, const FileAndHash *const *b)
241 {
242 	if ((*a)->file->length < (*b)->file->length)
243 		return -1;
244 	if ((*a)->file->length > (*b)->file->length)
245 		return 1;
246 
247 	return memcmp((*a)->file->data, (*b)->file->data, (*b)->file->length);
248 }
249 
250 static int
mft_fh_cmp_hash(const FileAndHash * const * a,const FileAndHash * const * b)251 mft_fh_cmp_hash(const FileAndHash *const *a, const FileAndHash *const *b)
252 {
253 	assert((*a)->hash->length == SHA256_DIGEST_LENGTH);
254 	assert((*b)->hash->length == SHA256_DIGEST_LENGTH);
255 
256 	return memcmp((*a)->hash->data, (*b)->hash->data, (*b)->hash->length);
257 }
258 
259 /*
260  * Assuming that the hash lengths are validated, this checks that all file names
261  * and hashes in a manifest are unique. Returns 1 on success, 0 on failure.
262  */
263 static int
mft_has_unique_names_and_hashes(const char * fn,const Manifest * mft)264 mft_has_unique_names_and_hashes(const char *fn, const Manifest *mft)
265 {
266 	STACK_OF(FileAndHash)	*fhs;
267 	int			 i, ret = 0;
268 
269 	if ((fhs = sk_FileAndHash_dup(mft->fileList)) == NULL)
270 		err(1, NULL);
271 
272 	(void)sk_FileAndHash_set_cmp_func(fhs, mft_fh_cmp_name);
273 	sk_FileAndHash_sort(fhs);
274 
275 	for (i = 0; i < sk_FileAndHash_num(fhs) - 1; i++) {
276 		const FileAndHash *curr = sk_FileAndHash_value(fhs, i);
277 		const FileAndHash *next = sk_FileAndHash_value(fhs, i + 1);
278 
279 		if (mft_fh_cmp_name(&curr, &next) == 0) {
280 			warnx("%s: duplicate name: %.*s", fn,
281 			    curr->file->length, curr->file->data);
282 			goto err;
283 		}
284 	}
285 
286 	(void)sk_FileAndHash_set_cmp_func(fhs, mft_fh_cmp_hash);
287 	sk_FileAndHash_sort(fhs);
288 
289 	for (i = 0; i < sk_FileAndHash_num(fhs) - 1; i++) {
290 		const FileAndHash *curr = sk_FileAndHash_value(fhs, i);
291 		const FileAndHash *next = sk_FileAndHash_value(fhs, i + 1);
292 
293 		if (mft_fh_cmp_hash(&curr, &next) == 0) {
294 			warnx("%s: duplicate hash for %.*s and %.*s", fn,
295 			    curr->file->length, curr->file->data,
296 			    next->file->length, next->file->data);
297 			goto err;
298 		}
299 	}
300 
301 	ret = 1;
302 
303  err:
304 	sk_FileAndHash_free(fhs);
305 
306 	return ret;
307 }
308 
309 /*
310  * Handle the eContent of the manifest object, RFC 6486 sec. 4.2.
311  * Returns 0 on failure and 1 on success.
312  */
313 static int
mft_parse_econtent(const char * fn,struct mft * mft,const unsigned char * d,size_t dsz)314 mft_parse_econtent(const char *fn, struct mft *mft, const unsigned char *d,
315     size_t dsz)
316 {
317 	const unsigned char	*oder;
318 	Manifest		*mft_asn1;
319 	FileAndHash		*fh;
320 	int			 found_crl, i, rc = 0;
321 
322 	oder = d;
323 	if ((mft_asn1 = d2i_Manifest(NULL, &d, dsz)) == NULL) {
324 		warnx("%s: RFC 6486 section 4: failed to parse Manifest", fn);
325 		goto out;
326 	}
327 	if (d != oder + dsz) {
328 		warnx("%s: %td bytes trailing garbage in eContent", fn,
329 		    oder + dsz - d);
330 		goto out;
331 	}
332 
333 	if (!valid_econtent_version(fn, mft_asn1->version, 0))
334 		goto out;
335 
336 	mft->seqnum = x509_convert_seqnum(fn, mft_asn1->manifestNumber);
337 	if (mft->seqnum == NULL)
338 		goto out;
339 
340 	/*
341 	 * OpenSSL's DER decoder implementation will accept a GeneralizedTime
342 	 * which doesn't conform to RFC 5280. So, double check.
343 	 */
344 	if (ASN1_STRING_length(mft_asn1->thisUpdate) != GENTIME_LENGTH) {
345 		warnx("%s: embedded from time format invalid", fn);
346 		goto out;
347 	}
348 	if (ASN1_STRING_length(mft_asn1->nextUpdate) != GENTIME_LENGTH) {
349 		warnx("%s: embedded until time format invalid", fn);
350 		goto out;
351 	}
352 
353 	if (!x509_get_time(mft_asn1->thisUpdate, &mft->thisupdate)) {
354 		warn("%s: parsing manifest thisUpdate failed", fn);
355 		goto out;
356 	}
357 	if (!x509_get_time(mft_asn1->nextUpdate, &mft->nextupdate)) {
358 		warn("%s: parsing manifest nextUpdate failed", fn);
359 		goto out;
360 	}
361 
362 	if (mft->thisupdate > mft->nextupdate) {
363 		warnx("%s: bad update interval", fn);
364 		goto out;
365 	}
366 
367 	if (OBJ_obj2nid(mft_asn1->fileHashAlg) != NID_sha256) {
368 		warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: "
369 		    "want SHA256 object, have %s (NID %d)", fn,
370 		    ASN1_tag2str(OBJ_obj2nid(mft_asn1->fileHashAlg)),
371 		    OBJ_obj2nid(mft_asn1->fileHashAlg));
372 		goto out;
373 	}
374 
375 	if (sk_FileAndHash_num(mft_asn1->fileList) >= MAX_MANIFEST_ENTRIES) {
376 		warnx("%s: %d exceeds manifest entry limit (%d)", fn,
377 		    sk_FileAndHash_num(mft_asn1->fileList),
378 		    MAX_MANIFEST_ENTRIES);
379 		goto out;
380 	}
381 
382 	mft->files = calloc(sk_FileAndHash_num(mft_asn1->fileList),
383 	    sizeof(struct mftfile));
384 	if (mft->files == NULL)
385 		err(1, NULL);
386 
387 	found_crl = 0;
388 	for (i = 0; i < sk_FileAndHash_num(mft_asn1->fileList); i++) {
389 		fh = sk_FileAndHash_value(mft_asn1->fileList, i);
390 		if (!mft_parse_filehash(fn, mft, fh, &found_crl))
391 			goto out;
392 	}
393 
394 	if (!found_crl) {
395 		warnx("%s: CRL not part of MFT fileList", fn);
396 		goto out;
397 	}
398 
399 	if (!mft_has_unique_names_and_hashes(fn, mft_asn1))
400 		goto out;
401 
402 	rc = 1;
403  out:
404 	Manifest_free(mft_asn1);
405 	return rc;
406 }
407 
408 /*
409  * Parse the objects that have been published in the manifest.
410  * Return mft if it conforms to RFC 6486, otherwise NULL.
411  */
412 struct mft *
mft_parse(X509 ** x509,const char * fn,int talid,const unsigned char * der,size_t len)413 mft_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
414     size_t len)
415 {
416 	struct mft	*mft;
417 	struct cert	*cert = NULL;
418 	int		 rc = 0;
419 	size_t		 cmsz;
420 	unsigned char	*cms;
421 	char		*crldp = NULL, *crlfile;
422 	time_t		 signtime = 0;
423 
424 	cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz, &signtime);
425 	if (cms == NULL)
426 		return NULL;
427 	assert(*x509 != NULL);
428 
429 	if ((mft = calloc(1, sizeof(*mft))) == NULL)
430 		err(1, NULL);
431 	mft->signtime = signtime;
432 
433 	if (!x509_get_aia(*x509, fn, &mft->aia))
434 		goto out;
435 	if (!x509_get_aki(*x509, fn, &mft->aki))
436 		goto out;
437 	if (!x509_get_sia(*x509, fn, &mft->sia))
438 		goto out;
439 	if (!x509_get_ski(*x509, fn, &mft->ski))
440 		goto out;
441 	if (mft->aia == NULL || mft->aki == NULL || mft->sia == NULL ||
442 	    mft->ski == NULL) {
443 		warnx("%s: RFC 6487 section 4.8: "
444 		    "missing AIA, AKI, SIA, or SKI X509 extension", fn);
445 		goto out;
446 	}
447 
448 	if (!x509_inherits(*x509)) {
449 		warnx("%s: RFC 3779 extension not set to inherit", fn);
450 		goto out;
451 	}
452 
453 	/* get CRL info for later */
454 	if (!x509_get_crl(*x509, fn, &crldp))
455 		goto out;
456 	if (crldp == NULL) {
457 		warnx("%s: RFC 6487 section 4.8.6: CRL: "
458 		    "missing CRL distribution point extension", fn);
459 		goto out;
460 	}
461 	crlfile = strrchr(crldp, '/');
462 	if (crlfile == NULL) {
463 		warnx("%s: RFC 6487 section 4.8.6: "
464 		    "invalid CRL distribution point", fn);
465 		goto out;
466 	}
467 	crlfile++;
468 	if (!valid_mft_filename(crlfile, strlen(crlfile)) ||
469 	    rtype_from_file_extension(crlfile) != RTYPE_CRL) {
470 		warnx("%s: RFC 6487 section 4.8.6: CRL: "
471 		    "bad CRL distribution point extension", fn);
472 		goto out;
473 	}
474 	if ((mft->crl = strdup(crlfile)) == NULL)
475 		err(1, NULL);
476 
477 	if (mft_parse_econtent(fn, mft, cms, cmsz) == 0)
478 		goto out;
479 
480 	if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
481 		goto out;
482 
483 	if (mft->signtime > mft->nextupdate) {
484 		warnx("%s: dating issue: CMS signing-time after MFT nextUpdate",
485 		    fn);
486 		goto out;
487 	}
488 
489 	rc = 1;
490 out:
491 	if (rc == 0) {
492 		mft_free(mft);
493 		mft = NULL;
494 		X509_free(*x509);
495 		*x509 = NULL;
496 	}
497 	free(crldp);
498 	cert_free(cert);
499 	free(cms);
500 	return mft;
501 }
502 
503 /*
504  * Free an MFT pointer.
505  * Safe to call with NULL.
506  */
507 void
mft_free(struct mft * p)508 mft_free(struct mft *p)
509 {
510 	size_t	 i;
511 
512 	if (p == NULL)
513 		return;
514 
515 	for (i = 0; i < p->filesz; i++)
516 		free(p->files[i].file);
517 
518 	free(p->path);
519 	free(p->files);
520 	free(p->seqnum);
521 	free(p->aia);
522 	free(p->aki);
523 	free(p->sia);
524 	free(p->ski);
525 	free(p->crl);
526 	free(p);
527 }
528 
529 /*
530  * Serialise MFT parsed content into the given buffer.
531  * See mft_read() for the other side of the pipe.
532  */
533 void
mft_buffer(struct ibuf * b,const struct mft * p)534 mft_buffer(struct ibuf *b, const struct mft *p)
535 {
536 	size_t		 i;
537 
538 	io_simple_buffer(b, &p->repoid, sizeof(p->repoid));
539 	io_simple_buffer(b, &p->talid, sizeof(p->talid));
540 	io_simple_buffer(b, &p->certid, sizeof(p->certid));
541 	io_str_buffer(b, p->path);
542 
543 	io_str_buffer(b, p->aia);
544 	io_str_buffer(b, p->aki);
545 	io_str_buffer(b, p->ski);
546 
547 	io_simple_buffer(b, &p->filesz, sizeof(size_t));
548 	for (i = 0; i < p->filesz; i++) {
549 		io_str_buffer(b, p->files[i].file);
550 		io_simple_buffer(b, &p->files[i].type,
551 		    sizeof(p->files[i].type));
552 		io_simple_buffer(b, &p->files[i].location,
553 		    sizeof(p->files[i].location));
554 		io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
555 	}
556 }
557 
558 /*
559  * Read an MFT structure from the file descriptor.
560  * Result must be passed to mft_free().
561  */
562 struct mft *
mft_read(struct ibuf * b)563 mft_read(struct ibuf *b)
564 {
565 	struct mft	*p = NULL;
566 	size_t		 i;
567 
568 	if ((p = calloc(1, sizeof(struct mft))) == NULL)
569 		err(1, NULL);
570 
571 	io_read_buf(b, &p->repoid, sizeof(p->repoid));
572 	io_read_buf(b, &p->talid, sizeof(p->talid));
573 	io_read_buf(b, &p->certid, sizeof(p->certid));
574 	io_read_str(b, &p->path);
575 
576 	io_read_str(b, &p->aia);
577 	io_read_str(b, &p->aki);
578 	io_read_str(b, &p->ski);
579 	assert(p->aia && p->aki && p->ski);
580 
581 	io_read_buf(b, &p->filesz, sizeof(size_t));
582 	if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL)
583 		err(1, NULL);
584 
585 	for (i = 0; i < p->filesz; i++) {
586 		io_read_str(b, &p->files[i].file);
587 		io_read_buf(b, &p->files[i].type, sizeof(p->files[i].type));
588 		io_read_buf(b, &p->files[i].location,
589 		    sizeof(p->files[i].location));
590 		io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
591 	}
592 
593 	return p;
594 }
595 
596 /*
597  * Compare the thisupdate time of two mft files.
598  */
599 int
mft_compare_issued(const struct mft * a,const struct mft * b)600 mft_compare_issued(const struct mft *a, const struct mft *b)
601 {
602 	if (a->thisupdate > b->thisupdate)
603 		return 1;
604 	if (a->thisupdate < b->thisupdate)
605 		return -1;
606 	return 0;
607 }
608 
609 /*
610  * Compare the manifestNumber of two mft files.
611  */
612 int
mft_compare_seqnum(const struct mft * a,const struct mft * b)613 mft_compare_seqnum(const struct mft *a, const struct mft *b)
614 {
615 	int r;
616 
617 	r = strlen(a->seqnum) - strlen(b->seqnum);
618 	if (r > 0)	/* seqnum in a is longer -> higher */
619 		return 1;
620 	if (r < 0)	/* seqnum in a is shorter -> smaller */
621 		return -1;
622 
623 	r = strcmp(a->seqnum, b->seqnum);
624 	if (r > 0)	/* a is greater, prefer a */
625 		return 1;
626 	if (r < 0)	/* b is greater, prefer b */
627 		return -1;
628 
629 	return 0;
630 }
631