138032Speter/*-
2261363Sgshapiro * Copyright (c) 2017-2018, Juniper Networks, Inc.
364562Sgshapiro *
438032Speter * Redistribution and use in source and binary forms, with or without
538032Speter * modification, are permitted provided that the following conditions
638032Speter * are met:
738032Speter * 1. Redistributions of source code must retain the above copyright
838032Speter *    notice, this list of conditions and the following disclaimer.
938032Speter * 2. Redistributions in binary form must reproduce the above copyright
1038032Speter *    notice, this list of conditions and the following disclaimer in the
1138032Speter *    documentation and/or other materials provided with the distribution.
1238032Speter *
1338032Speter * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1464562Sgshapiro * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1538032Speter * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1638032Speter * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1764562Sgshapiro * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
1890792Sgshapiro * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
1964562Sgshapiro * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20132943Sgshapiro * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21132943Sgshapiro * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22132943Sgshapiro * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23132943Sgshapiro * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2464562Sgshapiro */
2538032Speter#include <sys/cdefs.h>
2664562Sgshapiro#include <sys/queue.h>
2738032Speter
2864562Sgshapiro#include "libsecureboot-priv.h"
2938032Speter
3064562Sgshapiro
3164562Sgshapirostruct fingerprint_info {
3264562Sgshapiro	char		*fi_prefix;	/**< manifest entries relative to */
3364562Sgshapiro	char		*fi_skip;	/**< manifest entries prefixed with  */
3464562Sgshapiro	const char 	*fi_data;	/**< manifest data */
3590792Sgshapiro	size_t		fi_prefix_len;	/**< length of prefix */
3664562Sgshapiro	size_t		fi_skip_len;	/**< length of skip */
3764562Sgshapiro	dev_t		fi_dev;		/**< device id  */
3864562Sgshapiro	LIST_ENTRY(fingerprint_info) entries;
3964562Sgshapiro};
4038032Speter
4138032Speterstatic LIST_HEAD(, fingerprint_info) fi_list;
4264562Sgshapiro
4338032Speterstatic void
4464562Sgshapirofingerprint_info_init(void)
4590792Sgshapiro{
4690792Sgshapiro	static int once;
4790792Sgshapiro
4890792Sgshapiro	if (once)
4990792Sgshapiro		return;
5090792Sgshapiro	LIST_INIT(&fi_list);
5190792Sgshapiro	once = 1;
5290792Sgshapiro}
5390792Sgshapiro
5490792Sgshapiro/**
55266692Sgshapiro * @brief
5690792Sgshapiro * add manifest data to list
5790792Sgshapiro *
5890792Sgshapiro * list is kept sorted by longest prefix.
5964562Sgshapiro *
6064562Sgshapiro * @param[in] prefix
6190792Sgshapiro *	path that all manifest entries are resolved via
6290792Sgshapiro *
6390792Sgshapiro * @param[in] skip
6490792Sgshapiro *	optional prefix within manifest entries which should be skipped
6590792Sgshapiro *
6690792Sgshapiro * @param[in] data
6790792Sgshapiro *	manifest data
6890792Sgshapiro */
6990792Sgshapirovoid
7090792Sgshapirofingerprint_info_add(const char *filename, const char *prefix,
7190792Sgshapiro    const char *skip, const char *data, struct stat *stp)
7290792Sgshapiro{
73168515Sgshapiro	struct fingerprint_info *fip, *nfip, *lfip;
7438032Speter	char *cp;
7564562Sgshapiro	int n;
7638032Speter
7764562Sgshapiro	fingerprint_info_init();
7838032Speter	nfip = malloc(sizeof(struct fingerprint_info));
7964562Sgshapiro	if (nfip == NULL) {
8064562Sgshapiro#ifdef _STANDALONE
8164562Sgshapiro		printf("%s: out of memory! %lu\n", __func__,
8264562Sgshapiro		    (unsigned long)sizeof(struct fingerprint_info));
8364562Sgshapiro#endif
8438032Speter		return;
8564562Sgshapiro	}
8664562Sgshapiro	if (prefix) {
8764562Sgshapiro		nfip->fi_prefix = strdup(prefix);
8864562Sgshapiro	} else {
8964562Sgshapiro		if (!filename) {
9064562Sgshapiro			free(nfip);
9164562Sgshapiro			return;
9264562Sgshapiro		}
9364562Sgshapiro		nfip->fi_prefix = strdup(filename);
9464562Sgshapiro		cp = strrchr(nfip->fi_prefix, '/');
9564562Sgshapiro		if (cp == nfip->fi_prefix) {
9638032Speter			cp[1] = '\0';
9764562Sgshapiro		} else if (cp) {
9864562Sgshapiro			*cp = '\0';
9938032Speter		} else {
10064562Sgshapiro			free(nfip->fi_prefix);
10164562Sgshapiro			free(nfip);
10238032Speter			return;
10364562Sgshapiro		}
10464562Sgshapiro	}
10538032Speter	/* collapse any trailing ..[/] */
10664562Sgshapiro	n = 0;
10764562Sgshapiro	while ((cp = strrchr(nfip->fi_prefix, '/')) > nfip->fi_prefix) {
10864562Sgshapiro		if (cp[1] == '\0') {	/* trailing "/" */
10964562Sgshapiro			*cp = '\0';
11064562Sgshapiro			continue;
11164562Sgshapiro		}
11290792Sgshapiro		if (strcmp(&cp[1], "..") == 0) {
11390792Sgshapiro			n++;
11490792Sgshapiro			*cp = '\0';
11564562Sgshapiro			continue;
11638032Speter		}
11790792Sgshapiro		if (n > 0) {
11864562Sgshapiro			n--;
11964562Sgshapiro			*cp = '\0';
12064562Sgshapiro		}
12164562Sgshapiro		if (n == 0)
12264562Sgshapiro			break;
12338032Speter	}
12464562Sgshapiro	nfip->fi_dev = stp->st_dev;
12564562Sgshapiro#ifdef UNIT_TEST
12690792Sgshapiro	nfip->fi_dev = 0;
127249729Sgshapiro#endif
128249729Sgshapiro	nfip->fi_data = data;
129249729Sgshapiro	nfip->fi_prefix_len = strlen(nfip->fi_prefix);
130249729Sgshapiro	if (skip) {
131249729Sgshapiro		nfip->fi_skip_len = strlen(skip);
13290792Sgshapiro		if (nfip->fi_skip_len)
13364562Sgshapiro			nfip->fi_skip = strdup(skip);
13464562Sgshapiro		else
13564562Sgshapiro			nfip->fi_skip = NULL;
13698121Sgshapiro	} else {
13798121Sgshapiro		nfip->fi_skip = NULL;
13898121Sgshapiro		nfip->fi_skip_len = 0;
13998121Sgshapiro	}
140225906Sume
14198121Sgshapiro	if (LIST_EMPTY(&fi_list)) {
142225906Sume		LIST_INSERT_HEAD(&fi_list, nfip, entries);
143225906Sume		DEBUG_PRINTF(4, ("inserted %zu %s at head\n",
144261363Sgshapiro			nfip->fi_prefix_len, nfip->fi_prefix));
14598121Sgshapiro		return;
14698121Sgshapiro	}
14798121Sgshapiro	LIST_FOREACH(fip, &fi_list, entries) {
148225906Sume		if (nfip->fi_prefix_len >= fip->fi_prefix_len) {
14998121Sgshapiro			LIST_INSERT_BEFORE(fip, nfip, entries);
15064562Sgshapiro			DEBUG_PRINTF(4, ("inserted %zu %s before %zu %s\n",
15164562Sgshapiro				nfip->fi_prefix_len, nfip->fi_prefix,
15298121Sgshapiro				fip->fi_prefix_len, fip->fi_prefix));
15364562Sgshapiro			return;
15464562Sgshapiro		}
15598121Sgshapiro		lfip = fip;
15664562Sgshapiro	}
15764562Sgshapiro	LIST_INSERT_AFTER(lfip, nfip, entries);
15864562Sgshapiro	DEBUG_PRINTF(4, ("inserted %zu %s after %zu %s\n",
15964562Sgshapiro		nfip->fi_prefix_len, nfip->fi_prefix,
16098121Sgshapiro		lfip->fi_prefix_len, lfip->fi_prefix));
16164562Sgshapiro}
16264562Sgshapiro
16364562Sgshapiro#ifdef MANIFEST_SKIP_MAYBE
16464562Sgshapiro/*
16564562Sgshapiro * Deal with old incompatible boot/manifest
16664562Sgshapiro * if fp[-1] is '/' and start of entry matches
16764562Sgshapiro * MANIFEST_SKIP_MAYBE, we want it.
16864562Sgshapiro */
16938032Speterstatic char *
17038032Spetermaybe_skip(char *fp, struct fingerprint_info *fip, size_t *nplenp)
17138032Speter{
17238032Speter	char *tp;
17338032Speter
17438032Speter	tp = fp - sizeof(MANIFEST_SKIP_MAYBE);
17538032Speter
17638032Speter	if (tp >= fip->fi_data) {
17738032Speter		DEBUG_PRINTF(3, ("maybe: %.48s\n", tp));
17838032Speter		if ((tp == fip->fi_data || tp[-1] == '\n') &&
17938032Speter		    strncmp(tp, MANIFEST_SKIP_MAYBE,
18064562Sgshapiro			sizeof(MANIFEST_SKIP_MAYBE) - 1) == 0) {
18164562Sgshapiro			fp = tp;
18264562Sgshapiro			*nplenp += sizeof(MANIFEST_SKIP_MAYBE);
18364562Sgshapiro		}
18438032Speter	}
18538032Speter	return (fp);
18664562Sgshapiro}
18738032Speter#endif
18838032Speter
18964562Sgshapirochar *
19073188Sgshapirofingerprint_info_lookup(int fd, const char *path)
19173188Sgshapiro{
19273188Sgshapiro	char pbuf[MAXPATHLEN+1];
19338032Speter	char nbuf[MAXPATHLEN+1];
19464562Sgshapiro	struct stat st;
19564562Sgshapiro	struct fingerprint_info *fip;
19664562Sgshapiro	char *cp, *ep, *fp, *np;
19738032Speter	const char *prefix;
19864562Sgshapiro	size_t n, plen, nlen, nplen;
19964562Sgshapiro	dev_t dev = 0;
20064562Sgshapiro
20138032Speter	fingerprint_info_init();
20264562Sgshapiro
203120256Sgshapiro	n = strlcpy(pbuf, path, sizeof(pbuf));
204120256Sgshapiro	if (n >= sizeof(pbuf))
205120256Sgshapiro		return (NULL);
206120256Sgshapiro	if (fstat(fd, &st) == 0)
20790792Sgshapiro		dev = st.st_dev;
20890792Sgshapiro#ifdef UNIT_TEST
20990792Sgshapiro	dev = 0;
21090792Sgshapiro#endif
21190792Sgshapiro	/*
21290792Sgshapiro	 * get the first entry - it will have longest prefix
21390792Sgshapiro	 * so we can can work out how to initially split path
21490792Sgshapiro	 */
21590792Sgshapiro	fip = LIST_FIRST(&fi_list);
21690792Sgshapiro	if (!fip)
21790792Sgshapiro		return (NULL);
21890792Sgshapiro	prefix = pbuf;
21990792Sgshapiro	ep = NULL;
22090792Sgshapiro	cp = &pbuf[fip->fi_prefix_len];
22190792Sgshapiro	do {
22290792Sgshapiro		if (ep) {
22338032Speter			*ep = '/';
22438032Speter			cp -= 2;
22538032Speter			if (cp < pbuf)
22690792Sgshapiro				break;
22738032Speter		}
22890792Sgshapiro		nlen = plen = 0;	/* keep gcc quiet */
22938032Speter		if (cp > pbuf) {
23038032Speter			for ( ; cp >= pbuf && *cp != '/'; cp--)
23138032Speter				;	/* nothing */
23238032Speter			if (cp > pbuf) {
23338032Speter				ep = cp++;
23438032Speter				*ep = '\0';
23538032Speter			} else {
23638032Speter				cp = pbuf;
23738032Speter			}
23838032Speter			if (ep) {
23938032Speter				plen = ep - pbuf;
24090792Sgshapiro				nlen = n - plen - 1;
24138032Speter			}
24238032Speter		}
24338032Speter		if (cp == pbuf) {
24438032Speter			prefix = "/";
24538032Speter			plen = 1;
24638032Speter			if (*cp == '/') {
24738032Speter				nlen = n - 1;
24838032Speter				cp++;
24990792Sgshapiro			} else
25090792Sgshapiro				nlen = n;
25190792Sgshapiro			ep = NULL;
25290792Sgshapiro		}
25338032Speter
25438032Speter		DEBUG_PRINTF(2, ("looking for %s %zu %s\n", prefix, plen, cp));
25538032Speter
25638032Speter		LIST_FOREACH(fip, &fi_list, entries) {
25738032Speter			DEBUG_PRINTF(4, ("at %zu %s\n",
25864562Sgshapiro				fip->fi_prefix_len, fip->fi_prefix));
25990792Sgshapiro
26090792Sgshapiro			if (fip->fi_prefix_len < plen) {
26190792Sgshapiro				DEBUG_PRINTF(3, ("skipping prefix=%s %zu %zu\n",
26290792Sgshapiro					fip->fi_prefix, fip->fi_prefix_len,
26338032Speter					plen));
26438032Speter				break;
26538032Speter			}
26638032Speter			if (fip->fi_prefix_len == plen) {
26764562Sgshapiro				if (fip->fi_dev != 0 && fip->fi_dev != dev) {
26864562Sgshapiro					DEBUG_PRINTF(3, (
26964562Sgshapiro						"skipping dev=%ld != %ld\n",
27064562Sgshapiro						(long)fip->fi_dev,
27164562Sgshapiro						(long)dev));
27264562Sgshapiro					continue;
27364562Sgshapiro				}
27464562Sgshapiro				if (strcmp(prefix, fip->fi_prefix)) {
27564562Sgshapiro					DEBUG_PRINTF(3, (
27664562Sgshapiro						"skipping prefix=%s\n",
27790792Sgshapiro						fip->fi_prefix));
27864562Sgshapiro					continue;
27964562Sgshapiro				}
28064562Sgshapiro				DEBUG_PRINTF(3, ("checking prefix=%s\n",
28164562Sgshapiro					fip->fi_prefix));
28264562Sgshapiro				if (fip->fi_skip_len) {
28390792Sgshapiro					np = nbuf;
28490792Sgshapiro					nplen = snprintf(nbuf, sizeof(nbuf),
28590792Sgshapiro					    "%s/%s",
28664562Sgshapiro					    fip->fi_skip, cp);
28764562Sgshapiro					nplen = MIN(nplen, sizeof(nbuf) - 1);
28838032Speter				} else {
28964562Sgshapiro					np = cp;
29038032Speter					nplen = nlen;
29164562Sgshapiro				}
29264562Sgshapiro				DEBUG_PRINTF(3, ("lookup: '%s'\n", np));
29364562Sgshapiro				if (!(fp = strstr(fip->fi_data, np)))
29464562Sgshapiro					continue;
29564562Sgshapiro#ifdef MANIFEST_SKIP_MAYBE
29690792Sgshapiro				if (fip->fi_skip_len == 0 &&
29790792Sgshapiro				    fp > fip->fi_data && fp[-1] == '/') {
29838032Speter					fp = maybe_skip(fp, fip, &nplen);
29990792Sgshapiro				}
30090792Sgshapiro#endif
30190792Sgshapiro				/*
30290792Sgshapiro				 * when we find a match:
30390792Sgshapiro				 * fp[nplen] will be space and
30490792Sgshapiro				 * fp will be fip->fi_data or
30590792Sgshapiro				 * fp[-1] will be \n
30690792Sgshapiro				 */
30790792Sgshapiro				if (!((fp == fip->fi_data || fp[-1] == '\n') &&
30890792Sgshapiro					fp[nplen] == ' ')) {
30990792Sgshapiro					do {
31090792Sgshapiro						fp++;
31190792Sgshapiro						fp = strstr(fp, np);
31290792Sgshapiro						if (fp) {
31390792Sgshapiro#ifdef MANIFEST_SKIP_MAYBE
314102528Sgshapiro							if (fip->fi_skip_len == 0 &&
31590792Sgshapiro							    fp > fip->fi_data &&
31664562Sgshapiro							    fp[-1] == '/') {
31764562Sgshapiro								fp = maybe_skip(fp, fip, &nplen);
31864562Sgshapiro							}
31964562Sgshapiro#endif
32064562Sgshapiro							DEBUG_PRINTF(3,
32190792Sgshapiro							    ("fp[-1]=%#x fp[%zu]=%#x fp=%.78s\n",
32264562Sgshapiro								fp[-1], nplen,
32364562Sgshapiro								fp[nplen],
32464562Sgshapiro								fp));
32564562Sgshapiro						}
32664562Sgshapiro					} while (fp != NULL &&
32790792Sgshapiro					    !(fp[-1] == '\n' &&
32864562Sgshapiro						fp[nplen] == ' '));
32990792Sgshapiro					if (!fp)
33090792Sgshapiro						continue;
33164562Sgshapiro				}
33290792Sgshapiro				DEBUG_PRINTF(2, ("found %.78s\n", fp));
33390792Sgshapiro				/* we have a match! */
33464562Sgshapiro				for (cp = &fp[nplen]; *cp == ' '; cp++)
33590792Sgshapiro					; /* nothing */
336173340Sgshapiro				return (cp);
337173340Sgshapiro			} else {
33890792Sgshapiro				DEBUG_PRINTF(3,
339223067Sgshapiro				    ("Ignoring prefix=%s\n", fip->fi_prefix));
34064562Sgshapiro			}
34164562Sgshapiro		}
34264562Sgshapiro	} while (cp > &pbuf[1]);
34364562Sgshapiro
34464562Sgshapiro	return (NULL);
34538032Speter}
346168515Sgshapiro
347111823Sgshapirostatic int
34864562Sgshapiroverify_fingerprint(int fd, const char *path, const char *cp, off_t off)
34964562Sgshapiro{
35064562Sgshapiro	unsigned char buf[PAGE_SIZE];
35190792Sgshapiro	const br_hash_class *md;
35290792Sgshapiro	br_hash_compat_context mctx;
35390792Sgshapiro	size_t hlen;
354132943Sgshapiro	int n;
355132943Sgshapiro
35638032Speter	if (strncmp(cp, "no_hash", 7) == 0) {
35764562Sgshapiro		return (VE_FINGERPRINT_IGNORE);
35890792Sgshapiro	} else if (strncmp(cp, "sha256=", 7) == 0) {
35938032Speter		md = &br_sha256_vtable;
36038032Speter		hlen = br_sha256_SIZE;
36190792Sgshapiro		cp += 7;
36264562Sgshapiro#ifdef VE_SHA1_SUPPORT
36390792Sgshapiro	} else if (strncmp(cp, "sha1=", 5) == 0) {
36464562Sgshapiro		md = &br_sha1_vtable;
365168515Sgshapiro		hlen = br_sha1_SIZE;
366168515Sgshapiro		cp += 5;
367168515Sgshapiro#endif
368168515Sgshapiro#ifdef VE_SHA384_SUPPORT
369168515Sgshapiro	} else if (strncmp(cp, "sha384=", 7) == 0) {
370168515Sgshapiro		md = &br_sha384_vtable;
37164562Sgshapiro		hlen = br_sha384_SIZE;
37290792Sgshapiro		cp += 7;
37390792Sgshapiro#endif
37490792Sgshapiro#ifdef VE_SHA512_SUPPORT
37590792Sgshapiro	} else if (strncmp(cp, "sha512=", 7) == 0) {
376168515Sgshapiro		md = &br_sha512_vtable;
377168515Sgshapiro		hlen = br_sha512_SIZE;
378168515Sgshapiro		cp += 7;
379168515Sgshapiro#endif
380168515Sgshapiro	} else {
381168515Sgshapiro		ve_error_set("%s: no supported fingerprint", path);
382168515Sgshapiro		return (VE_FINGERPRINT_UNKNOWN);
383168515Sgshapiro	}
38438032Speter
38538032Speter	md->init(&mctx.vtable);
38638032Speter	if (off)
38738032Speter		lseek(fd, 0, SEEK_SET);
38838032Speter	do {
38938032Speter		n = read(fd, buf, sizeof(buf));
39038032Speter		if (n < 0)
39138032Speter			return (n);
39238032Speter		if (n > 0)
39338032Speter			md->update(&mctx.vtable, buf, n);
39438032Speter	} while (n > 0);
39538032Speter	lseek(fd, off, SEEK_SET);
39638032Speter	return (ve_check_hash(&mctx, md, path, cp, hlen));
39738032Speter}
39838032Speter
39938032Speter
40038032Speter/**
40138032Speter * @brief
40264562Sgshapiro * verify an open file
40338032Speter *
40438032Speter * @param[in] fd
40538032Speter *	open descriptor
40638032Speter *
40738032Speter * @param[in] path
40838032Speter *	pathname to open
40938032Speter *
41038032Speter * @param[in] off
41138032Speter *	current offset
41238032Speter *
41364562Sgshapiro * @return 0, VE_FINGERPRINT_OK or VE_FINGERPRINT_NONE, VE_FINGERPRINT_WRONG
41438032Speter */
41564562Sgshapiroint
41638032Speterverify_fd(int fd, const char *path, off_t off, struct stat *stp)
41738032Speter{
41838032Speter	struct stat st;
41964562Sgshapiro	char *cp;
42064562Sgshapiro	int rc;
42190792Sgshapiro
42238032Speter	if (!stp) {
42338032Speter		if (fstat(fd, &st) == 0)
42438032Speter			stp = &st;
42590792Sgshapiro	}
42664562Sgshapiro	if (stp && !S_ISREG(stp->st_mode))
42764562Sgshapiro		return (0);		/* not relevant */
428141858Sgshapiro	cp = fingerprint_info_lookup(fd, path);
42964562Sgshapiro	if (!cp) {
43064562Sgshapiro		ve_error_set("%s: no entry", path);
43164562Sgshapiro		return (VE_FINGERPRINT_NONE);
43238032Speter	}
43364562Sgshapiro	rc = verify_fingerprint(fd, path, cp, off);
43464562Sgshapiro	switch (rc) {
43564562Sgshapiro	case VE_FINGERPRINT_OK:
43638032Speter	case VE_FINGERPRINT_IGNORE:
43764562Sgshapiro	case VE_FINGERPRINT_UNKNOWN:
43864562Sgshapiro		return (rc);
43964562Sgshapiro	default:
44064562Sgshapiro		return (VE_FINGERPRINT_WRONG);
44164562Sgshapiro	}
44264562Sgshapiro}
44364562Sgshapiro
44464562Sgshapiro/**
44564562Sgshapiro * @brief
44664562Sgshapiro * open a file if it can be verified
44764562Sgshapiro *
44838032Speter * @param[in] path
44964562Sgshapiro *	pathname to open
45064562Sgshapiro *
45164562Sgshapiro * @param[in] flags
45264562Sgshapiro *	flags for open
45338032Speter *
45464562Sgshapiro * @return fd or VE_FINGERPRINT_NONE, VE_FINGERPRINT_WRONG
45564562Sgshapiro */
45664562Sgshapiroint
45764562Sgshapiroverify_open(const char *path, int flags)
45864562Sgshapiro{
45964562Sgshapiro	int fd;
46064562Sgshapiro	int rc;
46164562Sgshapiro
46238032Speter	if ((fd = open(path, flags)) >= 0) {
46364562Sgshapiro		if ((rc = verify_fd(fd, path, 0, NULL)) < 0) {
464132943Sgshapiro			close(fd);
46538032Speter			fd = rc;
46664562Sgshapiro		}
46764562Sgshapiro	}
46890792Sgshapiro	return (fd);
46964562Sgshapiro}
47064562Sgshapiro