From 857bc3bf0927ceb9764dccd2b26e78a5c1ed9549 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Sun, 12 Jul 1998 19:45:31 +0000 Subject: something hosed the sendmail import; fix up by hand --- usr.sbin/sendmail/makemap/makemap.8 | 43 ++--- usr.sbin/sendmail/makemap/makemap.c | 364 ++++++++++++++++++++++++++++-------- 2 files changed, 298 insertions(+), 109 deletions(-) (limited to 'usr.sbin/sendmail/makemap') diff --git a/usr.sbin/sendmail/makemap/makemap.8 b/usr.sbin/sendmail/makemap/makemap.8 index 3c1f6fad260..6180c8bda8b 100644 --- a/usr.sbin/sendmail/makemap/makemap.8 +++ b/usr.sbin/sendmail/makemap/makemap.8 @@ -1,35 +1,13 @@ +.\" Copyright (c) 1998 Sendmail, Inc. All rights reserved. .\" Copyright (c) 1988, 1991, 1993 .\" The Regents of the University of California. All rights reserved. .\" -.\" Redistribution and use in source and binary forms, with or without -.\" modification, are permitted provided that the following conditions -.\" are met: -.\" 1. Redistributions of source code must retain the above copyright -.\" notice, this list of conditions and the following disclaimer. -.\" 2. Redistributions in binary form must reproduce the above copyright -.\" notice, this list of conditions and the following disclaimer in the -.\" documentation and/or other materials provided with the distribution. -.\" 3. All advertising materials mentioning features or use of this software -.\" must display the following acknowledgement: -.\" This product includes software developed by the University of -.\" California, Berkeley and its contributors. -.\" 4. Neither the name of the University nor the names of its contributors -.\" may be used to endorse or promote products derived from this software -.\" without specific prior written permission. +.\" By using this file, you agree to the terms and conditions set +.\" forth in the LICENSE file which can be found at the top level of +.\" the sendmail distribution. .\" -.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND -.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -.\" SUCH DAMAGE. .\" -.\" @(#)makemap.8 8.4 (Berkeley) 7/23/97 +.\" @(#)makemap.8 8.10 (Berkeley) 5/19/98 .\" .Dd November 16, 1992 .Dt MAKEMAP 8 @@ -44,6 +22,7 @@ .Op Fl f .Op Fl o .Op Fl r +.Op Fl s .Op Fl v .Ar maptype .Ar mapname @@ -70,13 +49,11 @@ This requires the library. .It Li btree B-Tree format maps. -This requires the new Berkeley -.Xr db 3 +This requires the new Berkeley DB library. .It Li hash Hash format maps. -This also requires the -.Xr db 3 +This also requires the Berkeley DB library. .El .Pp @@ -122,6 +99,10 @@ Normally .Nm complains if you repeat a key, and does not do the insert. +.It Fl s +Ignore safety checks on maps being created. +This includes checking for hard or symbolic +links in world writable directories. .It Fl v Verbosely print what it is doing. .El diff --git a/usr.sbin/sendmail/makemap/makemap.c b/usr.sbin/sendmail/makemap/makemap.c index 3a04a0b6cd5..53b5865606a 100644 --- a/usr.sbin/sendmail/makemap/makemap.c +++ b/usr.sbin/sendmail/makemap/makemap.c @@ -1,39 +1,17 @@ /* - * Copyright (c) 1992 Eric P. Allman. + * Copyright (c) 1998 Sendmail, Inc. All rights reserved. + * Copyright (c) 1992 Eric P. Allman. All rights reserved. * Copyright (c) 1992, 1993 * The Regents of the University of California. All rights reserved. * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the sendmail distribution. * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. */ #ifndef lint -static char sccsid[] = "@(#)makemap.c 8.38 (Berkeley) 9/23/97"; +static char sccsid[] = "@(#)makemap.c 8.62 (Berkeley) 6/24/98"; #endif /* not lint */ #include @@ -42,13 +20,17 @@ static char sccsid[] = "@(#)makemap.c 8.38 (Berkeley) 9/23/97"; # include #endif #include "sendmail.h" +#include "pathnames.h" #ifdef NDBM -#include +# include #endif #ifdef NEWDB -#include +# include +# ifndef DB_VERSION_MAJOR +# define DB_VERSION_MAJOR 1 +# endif #endif enum type { T_DBM, T_BTREE, T_HASH, T_ERR, T_UNKNOWN }; @@ -76,23 +58,25 @@ uid_t RunAsGid; char *RunAsUserName; int Verbose = 2; bool DontInitGroups = TRUE; -bool UnsafeGroupWrites = FALSE; +long DontBlameSendmail = DBS_SAFE; u_char tTdvect[100]; +uid_t TrustedFileUid = 0; #define BUFSIZE 1024 +int main(argc, argv) int argc; char **argv; { char *progname; + char *cfile; bool inclnull = FALSE; bool notrunc = FALSE; bool allowreplace = FALSE; bool allowdups = FALSE; bool verbose = FALSE; bool foldcase = TRUE; - bool ignoresafeties = FALSE; int exitstat; int opt; char *typename; @@ -105,6 +89,8 @@ main(argc, argv) long dbcachesize = 1024 * 1024; enum type type; int fd; + int sff = SFF_ROOTOK|SFF_REGONLY; + struct passwd *pw; union { #ifdef NDBM @@ -117,22 +103,33 @@ main(argc, argv) } dbp; union dbent key, val; #ifdef NEWDB +# if DB_VERSION_MAJOR < 2 BTREEINFO bti; HASHINFO hinfo; +# else + DB_INFO dbinfo; +# endif #endif char ibuf[BUFSIZE]; char fbuf[MAXNAME]; char dbuf[MAXNAME]; +#ifdef NDBM char pbuf[MAXNAME]; +#endif +#if _FFR_TRUSTED_FILE_OWNER + FILE *cfp; + char buf[MAXLINE]; +#endif static char rnamebuf[MAXNAME]; /* holds RealUserName */ - struct passwd *pw; - int sff = SFF_ROOTOK|SFF_REGONLY|SFF_NOLINK|SFF_NOWLINK; - struct stat std, stp; + struct stat std; +#ifdef NDBM + struct stat stp; +#endif extern char *optarg; extern int optind; - extern bool lockfile(); progname = argv[0]; + cfile = _PATH_SENDMAILCF; RunAsUid = RealUid = getuid(); RunAsGid = RealGid = getgid(); @@ -144,18 +141,22 @@ main(argc, argv) sprintf(rnamebuf, "%s", pw->pw_name); } else - sprintf(rnamebuf, "Unknown UID %d", RealUid); + sprintf(rnamebuf, "Unknown UID %d", (int) RealUid); RunAsUserName = RealUserName = rnamebuf; #if _FFR_NEW_MAKEMAP_FLAGS -#define OPTIONS "Nc:dforsv" +#define OPTIONS "C:Nc:dforsv" #else -#define OPTIONS "Ndforv" +#define OPTIONS "C:Ndforsv" #endif - while ((opt = getopt(argc, argv, OPTIONS)) != -1) + while ((opt = getopt(argc, argv, OPTIONS)) != EOF) { switch (opt) { + case 'C': + cfile = optarg; + break; + case 'N': inclnull = TRUE; break; @@ -182,11 +183,9 @@ main(argc, argv) allowreplace = TRUE; break; -#if _FFR_NEW_MAKEMAP_FLAGS case 's': - ignoresafeties = TRUE; + DontBlameSendmail |= DBS_MAPINUNSAFEDIRPATH|DBS_WRITEMAPTOHARDLINK|DBS_WRITEMAPTOSYMLINK|DBS_LINKEDMAPINWRITABLEDIR; break; -#endif case 'v': verbose = TRUE; @@ -198,6 +197,13 @@ main(argc, argv) } } + if (!bitset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail)) + sff |= SFF_NOSLINK; + if (!bitset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail)) + sff |= SFF_NOHLINK; + if (!bitset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail)) + sff |= SFF_NOWLINK; + argc -= optind; argv += optind; if (argc != 2) @@ -226,6 +232,65 @@ main(argc, argv) type = T_UNKNOWN; } +#if _FFR_TRUSTED_FILE_OWNER + if ((cfp = fopen(cfile, "r")) == NULL) + { + fprintf(stderr, "mailstats: "); + perror(cfile); + exit(EX_NOINPUT); + } + while (fgets(buf, sizeof(buf), cfp) != NULL) + { + register char *b; + + if ((b = strchr(buf, '\n')) != NULL) + *b = '\0'; + + b = buf; + switch (*b++) + { + case 'O': /* option */ + if (strncasecmp(b, " TrustedFileOwner", 17) == 0 && + !(isascii(b[17]) && isalnum(b[17]))) + { + b = strchr(b, '='); + if (b == NULL) + continue; + while (isascii(*++b) && isspace(*b)) + continue; + if (isascii(*b) && isdigit(*b)) + TrustedFileUid = atoi(b); + else + { + register struct passwd *pw; + + TrustedFileUid = 0; + pw = getpwnam(b); + if (pw == NULL) + fprintf(stderr, + "TrustedFileOwner: unknown user %s", b); + else + TrustedFileUid = pw->pw_uid; + } + +# ifdef UID_MAX + if (TrustedFileUid > UID_MAX) + { + syserr("TrustedFileOwner: uid value (%ld) > UID_MAX (%ld)", + TrustedFileUid, UID_MAX); + TrustedFileUid = 0; + } +# endif + break; + } + + + default: + continue; + } + } + (void) fclose(cfp); +#endif switch (type) { case T_ERR: @@ -234,7 +299,7 @@ main(argc, argv) "Usage: %s [-N] [-c cachesize] [-d] [-f] [-o] [-r] [-s] [-v] type mapname\n", progname); #else - fprintf(stderr, "Usage: %s [-N] [-d] [-f] [-o] [-r] [-v] type mapname\n", progname); + fprintf(stderr, "Usage: %s [-N] [-d] [-f] [-o] [-r] [-s] [-v] type mapname\n", progname); #endif exit(EX_USAGE); @@ -256,21 +321,47 @@ main(argc, argv) #ifdef NEWDB case T_BTREE: +# if DB_VERSION_MAJOR < 2 bzero(&bti, sizeof bti); +# else + bzero(&dbinfo, sizeof dbinfo); +# endif if (allowdups) + { +# if DB_VERSION_MAJOR < 2 bti.flags |= R_DUP; +# else + dbinfo.flags |= DB_DUP; +# endif + } if (allowdups || allowreplace) putflags = 0; else + { +# if DB_VERSION_MAJOR < 2 putflags = R_NOOVERWRITE; +# else + putflags = DB_NOOVERWRITE; +# endif + } break; case T_HASH: +# if DB_VERSION_MAJOR < 2 bzero(&hinfo, sizeof hinfo); +# else + bzero(&dbinfo, sizeof dbinfo); +# endif if (allowreplace) putflags = 0; else + { +# if DB_VERSION_MAJOR < 2 putflags = R_NOOVERWRITE; +# else + putflags = DB_NOOVERWRITE; +# endif + } break; #endif #ifdef NDBM @@ -326,8 +417,7 @@ main(argc, argv) exit(EX_USAGE); } strcpy(dbuf, mapname); - if (!ignoresafeties && - (st = safefile(dbuf, RealUid, RealGid, RealUserName, + if ((st = safefile(dbuf, RealUid, RealGid, RealUserName, sff, S_IWUSR, &std)) != 0) { fprintf(stderr, @@ -347,7 +437,7 @@ main(argc, argv) } sprintf(dbuf, "%s.dir", mapname); if ((st = safefile(dbuf, RealUid, RealGid, RealUserName, - sff, S_IWUSR, &std)) != 0 && !ignoresafeties) + sff, S_IWUSR, &std)) != 0) { fprintf(stderr, "%s: could not create: %s\n", @@ -356,7 +446,7 @@ main(argc, argv) } sprintf(pbuf, "%s.pag", mapname); if ((st = safefile(pbuf, RealUid, RealGid, RealUserName, - sff, S_IWUSR, &stp)) != 0 && !ignoresafeties) + sff, S_IWUSR, &stp)) != 0) { fprintf(stderr, "%s: could not create: %s\n", @@ -384,10 +474,7 @@ main(argc, argv) mode |= O_EXLOCK; #else /* pre-lock the database */ - if (ignoresafeties) - fd = dfopen(dbuf, mode & ~O_TRUNC, 0644, sff); - else - fd = safeopen(dbuf, mode & ~O_TRUNC, 0644, sff); + fd = safeopen(dbuf, mode & ~O_TRUNC, 0644, sff); if (fd < 0) { fprintf(stderr, "%s: cannot create type %s map %s\n", @@ -408,9 +495,9 @@ main(argc, argv) dbm_close(dbp.dbm); exit(EX_CONFIG); } - if (!ignoresafeties && dbp.dbm != NULL && - (filechanged(dbuf, dbm_dirfno(dbp.dbm), &std, sff) || - filechanged(pbuf, dbm_pagfno(dbp.dbm), &stp, sff))) + if (dbp.dbm != NULL && + (filechanged(dbuf, dbm_dirfno(dbp.dbm), &std) || + filechanged(pbuf, dbm_pagfno(dbp.dbm), &stp))) { fprintf(stderr, "dbm map %s: file changed after open\n", @@ -418,56 +505,138 @@ main(argc, argv) dbm_close(dbp.dbm); exit(EX_CANTCREAT); } + if (geteuid() == 0 && TrustedFileUid != 0) + { + if (fchown(dbm_dirfno(dbp.dbm), TrustedFileUid, -1) < 0 || + fchown(dbm_pagfno(dbp.dbm), TrustedFileUid, -1) < 0) + { + fprintf(stderr, + "WARNING: ownership change on %s failed: %s", + mapname, errstring(errno)); + } + } + break; #endif #ifdef NEWDB case T_HASH: /* tweak some parameters for performance */ +# if DB_VERSION_MAJOR < 2 hinfo.nelem = 4096; hinfo.cachesize = dbcachesize; +# else + dbinfo.h_nelem = 4096; + dbinfo.db_cachesize = dbcachesize; +# endif +# if DB_VERSION_MAJOR < 2 dbp.db = dbopen(mapname, mode, 0644, DB_HASH, &hinfo); +# else + { + int flags = 0; + + if (bitset(O_CREAT, mode)) + flags |= DB_CREATE; + if (bitset(O_TRUNC, mode)) + flags |= DB_TRUNCATE; + + dbp.db = NULL; + errno = db_open(mapname, DB_HASH, flags, 0644, + NULL, &dbinfo, &dbp.db); + } +# endif if (dbp.db != NULL) { - if (!ignoresafeties && - filechanged(dbuf, dbp.db->fd(dbp.db), &std, sff)) + int fd; + +# if DB_VERSION_MAJOR < 2 + fd = dbp.db->fd(dbp.db); +# else + fd = -1; + errno = dbp.db->fd(dbp.db, &fd); +# endif + if (filechanged(dbuf, fd, &std)) { fprintf(stderr, "db map %s: file changed after open\n", mapname); +# if DB_VERSION_MAJOR < 2 dbp.db->close(dbp.db); +# else + errno = dbp.db->close(dbp.db, 0); +# endif exit(EX_CANTCREAT); } -# if OLD_NEWDB - (void) (*dbp.db->sync)(dbp.db); -# else (void) (*dbp.db->sync)(dbp.db, 0); -# endif + if (geteuid() == 0 && TrustedFileUid != 0) + { + if (fchown(fd, TrustedFileUid, -1) < 0) + { + fprintf(stderr, + "WARNING: ownership change on %s failed: %s", + mapname, errstring(errno)); + } + } } break; case T_BTREE: /* tweak some parameters for performance */ +# if DB_VERSION_MAJOR < 2 bti.cachesize = dbcachesize; +# else + dbinfo.db_cachesize = dbcachesize; +# endif +# if DB_VERSION_MAJOR < 2 dbp.db = dbopen(mapname, mode, 0644, DB_BTREE, &bti); +# else + { + int flags = 0; + + if (bitset(O_CREAT, mode)) + flags |= DB_CREATE; + if (bitset(O_TRUNC, mode)) + flags |= DB_TRUNCATE; + + dbp.db = NULL; + errno = db_open(mapname, DB_BTREE, flags, 0644, + NULL, &dbinfo, &dbp.db); + } +# endif if (dbp.db != NULL) { - if (!ignoresafeties && - filechanged(dbuf, dbp.db->fd(dbp.db), &std, sff)) + int fd; + +# if DB_VERSION_MAJOR < 2 + fd = dbp.db->fd(dbp.db); +# else + fd = -1; + errno = dbp.db->fd(dbp.db, &fd); +# endif + if (filechanged(dbuf, fd, &std)) { fprintf(stderr, "db map %s: file changed after open\n", mapname); +# if DB_VERSION_MAJOR < 2 dbp.db->close(dbp.db); +# else + errno = dbp.db->close(dbp.db, 0); +# endif exit(EX_CANTCREAT); } -# if OLD_NEWDB - (void) (*dbp.db->sync)(dbp.db); -# else (void) (*dbp.db->sync)(dbp.db, 0); -# endif + if (geteuid() == 0 && TrustedFileUid != 0) + { + if (fchown(fd, TrustedFileUid, -1) < 0) + { + fprintf(stderr, + "WARNING: ownership change on %s failed: %s", + mapname, errstring(errno)); + } + } } break; #endif @@ -506,23 +675,31 @@ main(argc, argv) *p = '\0'; else if (!feof(stdin)) { - fprintf(stderr, "%s: %s: line %d: line too long (%d bytes max)\n", - progname, mapname, lineno, sizeof ibuf); + fprintf(stderr, "%s: %s: line %d: line too long (%ld bytes max)\n", + progname, mapname, lineno, (long) sizeof ibuf); continue; } if (ibuf[0] == '\0' || ibuf[0] == '#') continue; - if (isspace(ibuf[0])) + if (isascii(ibuf[0]) && isspace(ibuf[0])) { fprintf(stderr, "%s: %s: line %d: syntax error (leading space)\n", progname, mapname, lineno); continue; } +#ifdef NEWDB + if (type == T_HASH || type == T_BTREE) + { + bzero(&key.db, sizeof key.db); + bzero(&val.db, sizeof val.db); + } +#endif + key.xx.data = ibuf; - for (p = ibuf; *p != '\0' && !isspace(*p); p++) + for (p = ibuf; *p != '\0' && !(isascii(*p) && isspace(*p)); p++) { - if (foldcase && isupper(*p)) + if (foldcase && isascii(*p) && isupper(*p)) *p = tolower(*p); } key.xx.size = p - key.xx.data; @@ -530,7 +707,7 @@ main(argc, argv) key.xx.size++; if (*p != '\0') *p++ = '\0'; - while (isspace(*p)) + while (isascii(*p) && isspace(*p)) p++; if (*p == '\0') { @@ -563,9 +740,30 @@ main(argc, argv) #ifdef NEWDB case T_BTREE: case T_HASH: +# if DB_VERSION_MAJOR < 2 st = (*dbp.db->put)(dbp.db, &key.db, &val.db, putflags); +# else + errno = (*dbp.db->put)(dbp.db, NULL, &key.db, + &val.db, putflags); + switch (errno) + { + case DB_KEYEXIST: + st = 1; + break; + + case 0: + st = 0; + break; + + default: + st = -1; + break; + } +# endif break; #endif + default: + break; } if (st < 0) @@ -598,7 +796,11 @@ main(argc, argv) #ifdef NEWDB case T_HASH: case T_BTREE: +# if DB_VERSION_MAJOR < 2 if ((*dbp.db->close)(dbp.db) < 0) +# else + if ((errno = (*dbp.db->close)(dbp.db, 0)) != 0) +# endif { fprintf(stderr, "%s: %s: error on close\n", progname, mapname); @@ -606,6 +808,8 @@ main(argc, argv) exitstat = EX_IOERR; } #endif + default: + break; } #if !O_EXLOCK @@ -680,7 +884,7 @@ lockfile(fd, filename, ext, type) return FALSE; } -/*VARARGS2*/ +/*VARARGS1*/ void #ifdef __STDC__ message(const char *msg, ...) @@ -694,7 +898,9 @@ message(msg, va_alist) VA_LOCAL_DECL m = msg; - if (isdigit(m[0]) && isdigit(m[1]) && isdigit(m[2]) && m[3] == ' ') + if (isascii(m[0]) && isdigit(m[0]) && + isascii(m[1]) && isdigit(m[1]) && + isascii(m[2]) && isdigit(m[2]) && m[3] == ' ') m += 4; VA_START(msg); vfprintf(stderr, m, ap); @@ -702,7 +908,7 @@ message(msg, va_alist) fprintf(stderr, "\n"); } -/*VARARGS2*/ +/*VARARGS1*/ void #ifdef __STDC__ syserr(const char *msg, ...) @@ -716,7 +922,9 @@ syserr(msg, va_alist) VA_LOCAL_DECL m = msg; - if (isdigit(m[0]) && isdigit(m[1]) && isdigit(m[2]) && m[3] == ' ') + if (isascii(m[0]) && isdigit(m[0]) && + isascii(m[1]) && isdigit(m[1]) && + isascii(m[2]) && isdigit(m[2]) && m[3] == ' ') m += 4; VA_START(msg); vfprintf(stderr, m, ap); @@ -771,7 +979,7 @@ errstring(err) #if HASSTRERROR return strerror(err); #else - if (err < 0 || err > sys_nerr) + if (err < 0 || err >= sys_nerr) { sprintf(errstr, "Error %d", err); return errstr; -- cgit v1.2.3