164562Sgshapiro/* 2261194Sgshapiro * Copyright (c) 1998-2004 Proofpoint, Inc. and its suppliers. 364562Sgshapiro * All rights reserved. 464562Sgshapiro * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. 564562Sgshapiro * Copyright (c) 1988, 1993 664562Sgshapiro * The Regents of the University of California. All rights reserved. 764562Sgshapiro * 864562Sgshapiro * By using this file, you agree to the terms and conditions set 964562Sgshapiro * forth in the LICENSE file which can be found at the top level of 1064562Sgshapiro * the sendmail distribution. 1164562Sgshapiro * 1264562Sgshapiro */ 1364562Sgshapiro 1464562Sgshapiro#include <sendmail.h> 1590792Sgshapiro#include <sm/io.h> 1690792Sgshapiro#include <sm/errstring.h> 1764562Sgshapiro 18266527SgshapiroSM_RCSID("@(#)$Id: safefile.c,v 8.130 2013-11-22 20:51:50 ca Exp $") 1964562Sgshapiro 2090792Sgshapiro 2190792Sgshapiro/* 2264562Sgshapiro** SAFEFILE -- return 0 if a file exists and is safe for a user. 2364562Sgshapiro** 2464562Sgshapiro** Parameters: 2564562Sgshapiro** fn -- filename to check. 2664562Sgshapiro** uid -- user id to compare against. 2764562Sgshapiro** gid -- group id to compare against. 28363466Sgshapiro** user -- user name to compare against (used for group sets). 2964562Sgshapiro** flags -- modifiers: 3064562Sgshapiro** SFF_MUSTOWN -- "uid" must own this file. 3164562Sgshapiro** SFF_NOSLINK -- file cannot be a symbolic link. 3264562Sgshapiro** mode -- mode bits that must match. 3364562Sgshapiro** st -- if set, points to a stat structure that will 3464562Sgshapiro** get the stat info for the file. 3564562Sgshapiro** 3664562Sgshapiro** Returns: 3764562Sgshapiro** 0 if fn exists, is owned by uid, and matches mode. 3864562Sgshapiro** An errno otherwise. The actual errno is cleared. 3964562Sgshapiro** 4064562Sgshapiro** Side Effects: 4164562Sgshapiro** none. 4264562Sgshapiro*/ 4364562Sgshapiro 4464562Sgshapiroint 4564562Sgshapirosafefile(fn, uid, gid, user, flags, mode, st) 4664562Sgshapiro char *fn; 4764562Sgshapiro UID_T uid; 4864562Sgshapiro GID_T gid; 4964562Sgshapiro char *user; 5064562Sgshapiro long flags; 5164562Sgshapiro int mode; 5264562Sgshapiro struct stat *st; 5364562Sgshapiro{ 5464562Sgshapiro register char *p; 5564562Sgshapiro register struct group *gr = NULL; 5664562Sgshapiro int file_errno = 0; 5764562Sgshapiro bool checkpath; 5864562Sgshapiro struct stat stbuf; 5964562Sgshapiro struct stat fstbuf; 6098121Sgshapiro char fbuf[MAXPATHLEN]; 6164562Sgshapiro 6264562Sgshapiro if (tTd(44, 4)) 6390792Sgshapiro sm_dprintf("safefile(%s, uid=%d, gid=%d, flags=%lx, mode=%o):\n", 6464562Sgshapiro fn, (int) uid, (int) gid, flags, mode); 6564562Sgshapiro errno = 0; 6690792Sgshapiro if (sm_strlcpy(fbuf, fn, sizeof fbuf) >= sizeof fbuf) 6764562Sgshapiro { 6864562Sgshapiro if (tTd(44, 4)) 6990792Sgshapiro sm_dprintf("\tpathname too long\n"); 7064562Sgshapiro return ENAMETOOLONG; 7164562Sgshapiro } 7264562Sgshapiro fn = fbuf; 7390792Sgshapiro if (st == NULL) 7490792Sgshapiro st = &fstbuf; 7564562Sgshapiro 7664562Sgshapiro /* ignore SFF_SAFEDIRPATH if we are debugging */ 7764562Sgshapiro if (RealUid != 0 && RunAsUid == RealUid) 7864562Sgshapiro flags &= ~SFF_SAFEDIRPATH; 7964562Sgshapiro 8064562Sgshapiro /* first check to see if the file exists at all */ 8164562Sgshapiro# if HASLSTAT 8264562Sgshapiro if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st) 8364562Sgshapiro : stat(fn, st)) < 0) 84363466Sgshapiro# else 8564562Sgshapiro if (stat(fn, st) < 0) 86363466Sgshapiro# endif 8764562Sgshapiro { 8864562Sgshapiro file_errno = errno; 8964562Sgshapiro } 9064562Sgshapiro else if (bitset(SFF_SETUIDOK, flags) && 9164562Sgshapiro !bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode) && 9264562Sgshapiro S_ISREG(st->st_mode)) 9364562Sgshapiro { 9464562Sgshapiro /* 9590792Sgshapiro ** If final file is set-user-ID, run as the owner of that 9664562Sgshapiro ** file. Gotta be careful not to reveal anything too 9764562Sgshapiro ** soon here! 9864562Sgshapiro */ 9964562Sgshapiro 10064562Sgshapiro# ifdef SUID_ROOT_FILES_OK 10164562Sgshapiro if (bitset(S_ISUID, st->st_mode)) 102363466Sgshapiro# else 10364562Sgshapiro if (bitset(S_ISUID, st->st_mode) && st->st_uid != 0 && 10464562Sgshapiro st->st_uid != TrustedUid) 105363466Sgshapiro# endif 10664562Sgshapiro { 10764562Sgshapiro uid = st->st_uid; 10864562Sgshapiro user = NULL; 10964562Sgshapiro } 11064562Sgshapiro# ifdef SUID_ROOT_FILES_OK 11164562Sgshapiro if (bitset(S_ISGID, st->st_mode)) 112363466Sgshapiro# else 11364562Sgshapiro if (bitset(S_ISGID, st->st_mode) && st->st_gid != 0) 114363466Sgshapiro# endif 11564562Sgshapiro gid = st->st_gid; 11664562Sgshapiro } 11764562Sgshapiro 11864562Sgshapiro checkpath = !bitset(SFF_NOPATHCHECK, flags) || 11964562Sgshapiro (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags)); 12064562Sgshapiro if (bitset(SFF_NOWLINK, flags) && !bitset(SFF_SAFEDIRPATH, flags)) 12164562Sgshapiro { 12264562Sgshapiro int ret; 12364562Sgshapiro 12464562Sgshapiro /* check the directory */ 12564562Sgshapiro p = strrchr(fn, '/'); 12664562Sgshapiro if (p == NULL) 12764562Sgshapiro { 12864562Sgshapiro ret = safedirpath(".", uid, gid, user, 12964562Sgshapiro flags|SFF_SAFEDIRPATH, 0, 0); 13064562Sgshapiro } 13164562Sgshapiro else 13264562Sgshapiro { 13364562Sgshapiro *p = '\0'; 13464562Sgshapiro ret = safedirpath(fn, uid, gid, user, 13564562Sgshapiro flags|SFF_SAFEDIRPATH, 0, 0); 13664562Sgshapiro *p = '/'; 13764562Sgshapiro } 13864562Sgshapiro if (ret == 0) 13964562Sgshapiro { 14064562Sgshapiro /* directory is safe */ 14190792Sgshapiro checkpath = false; 14264562Sgshapiro } 14364562Sgshapiro else 14464562Sgshapiro { 14564562Sgshapiro# if HASLSTAT 14664562Sgshapiro /* Need lstat() information if called stat() before */ 14764562Sgshapiro if (!bitset(SFF_NOSLINK, flags) && lstat(fn, st) < 0) 14864562Sgshapiro { 14964562Sgshapiro ret = errno; 15064562Sgshapiro if (tTd(44, 4)) 15190792Sgshapiro sm_dprintf("\t%s\n", sm_errstring(ret)); 15264562Sgshapiro return ret; 15364562Sgshapiro } 15464562Sgshapiro# endif /* HASLSTAT */ 15564562Sgshapiro /* directory is writable: disallow links */ 15664562Sgshapiro flags |= SFF_NOLINK; 15764562Sgshapiro } 15864562Sgshapiro } 15964562Sgshapiro 16064562Sgshapiro if (checkpath) 16164562Sgshapiro { 16264562Sgshapiro int ret; 16364562Sgshapiro 16464562Sgshapiro p = strrchr(fn, '/'); 16564562Sgshapiro if (p == NULL) 16664562Sgshapiro { 16764562Sgshapiro ret = safedirpath(".", uid, gid, user, flags, 0, 0); 16864562Sgshapiro } 16964562Sgshapiro else 17064562Sgshapiro { 17164562Sgshapiro *p = '\0'; 17264562Sgshapiro ret = safedirpath(fn, uid, gid, user, flags, 0, 0); 17364562Sgshapiro *p = '/'; 17464562Sgshapiro } 17564562Sgshapiro if (ret != 0) 17664562Sgshapiro return ret; 17764562Sgshapiro } 17864562Sgshapiro 17964562Sgshapiro /* 18064562Sgshapiro ** If the target file doesn't exist, check the directory to 18164562Sgshapiro ** ensure that it is writable by this user. 18264562Sgshapiro */ 18364562Sgshapiro 18464562Sgshapiro if (file_errno != 0) 18564562Sgshapiro { 18664562Sgshapiro int ret = file_errno; 18764562Sgshapiro char *dir = fn; 18864562Sgshapiro 18964562Sgshapiro if (tTd(44, 4)) 19090792Sgshapiro sm_dprintf("\t%s\n", sm_errstring(ret)); 19164562Sgshapiro 19264562Sgshapiro errno = 0; 19364562Sgshapiro if (!bitset(SFF_CREAT, flags) || file_errno != ENOENT) 19464562Sgshapiro return ret; 19564562Sgshapiro 19664562Sgshapiro /* check to see if legal to create the file */ 19764562Sgshapiro p = strrchr(dir, '/'); 19864562Sgshapiro if (p == NULL) 19964562Sgshapiro dir = "."; 20064562Sgshapiro else if (p == dir) 20164562Sgshapiro dir = "/"; 20264562Sgshapiro else 20364562Sgshapiro *p = '\0'; 20464562Sgshapiro if (stat(dir, &stbuf) >= 0) 20564562Sgshapiro { 20664562Sgshapiro int md = S_IWRITE|S_IEXEC; 20764562Sgshapiro 20890792Sgshapiro ret = 0; 20964562Sgshapiro if (stbuf.st_uid == uid) 21064562Sgshapiro /* EMPTY */ 21164562Sgshapiro ; 21264562Sgshapiro else if (uid == 0 && stbuf.st_uid == TrustedUid) 21364562Sgshapiro /* EMPTY */ 21464562Sgshapiro ; 21564562Sgshapiro else 21664562Sgshapiro { 21764562Sgshapiro md >>= 3; 21864562Sgshapiro if (stbuf.st_gid == gid) 21964562Sgshapiro /* EMPTY */ 22064562Sgshapiro ; 22164562Sgshapiro# ifndef NO_GROUP_SET 22264562Sgshapiro else if (user != NULL && !DontInitGroups && 22364562Sgshapiro ((gr != NULL && 22464562Sgshapiro gr->gr_gid == stbuf.st_gid) || 22564562Sgshapiro (gr = getgrgid(stbuf.st_gid)) != NULL)) 22664562Sgshapiro { 22764562Sgshapiro register char **gp; 22864562Sgshapiro 22964562Sgshapiro for (gp = gr->gr_mem; *gp != NULL; gp++) 23064562Sgshapiro if (strcmp(*gp, user) == 0) 23164562Sgshapiro break; 23264562Sgshapiro if (*gp == NULL) 23364562Sgshapiro md >>= 3; 23464562Sgshapiro } 23564562Sgshapiro# endif /* ! NO_GROUP_SET */ 23664562Sgshapiro else 23764562Sgshapiro md >>= 3; 23864562Sgshapiro } 23964562Sgshapiro if ((stbuf.st_mode & md) != md) 24090792Sgshapiro ret = errno = EACCES; 24164562Sgshapiro } 24290792Sgshapiro else 24390792Sgshapiro ret = errno; 24464562Sgshapiro if (tTd(44, 4)) 24590792Sgshapiro sm_dprintf("\t[final dir %s uid %d mode %lo] %s\n", 24690792Sgshapiro dir, (int) stbuf.st_uid, 24790792Sgshapiro (unsigned long) stbuf.st_mode, 24890792Sgshapiro sm_errstring(ret)); 24964562Sgshapiro if (p != NULL) 25064562Sgshapiro *p = '/'; 25164562Sgshapiro st->st_mode = ST_MODE_NOFILE; 25264562Sgshapiro return ret; 25364562Sgshapiro } 25464562Sgshapiro 25564562Sgshapiro# ifdef S_ISLNK 25664562Sgshapiro if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode)) 25764562Sgshapiro { 25864562Sgshapiro if (tTd(44, 4)) 25990792Sgshapiro sm_dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n", 26090792Sgshapiro (unsigned long) st->st_mode); 26164562Sgshapiro return E_SM_NOSLINK; 26264562Sgshapiro } 26364562Sgshapiro# endif /* S_ISLNK */ 26464562Sgshapiro if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode)) 26564562Sgshapiro { 26664562Sgshapiro if (tTd(44, 4)) 26790792Sgshapiro sm_dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n", 26890792Sgshapiro (unsigned long) st->st_mode); 26964562Sgshapiro return E_SM_REGONLY; 27064562Sgshapiro } 27164562Sgshapiro if (bitset(SFF_NOGWFILES, flags) && 27264562Sgshapiro bitset(S_IWGRP, st->st_mode)) 27364562Sgshapiro { 27464562Sgshapiro if (tTd(44, 4)) 27590792Sgshapiro sm_dprintf("\t[write bits %lo]\tE_SM_GWFILE\n", 27690792Sgshapiro (unsigned long) st->st_mode); 27764562Sgshapiro return E_SM_GWFILE; 27864562Sgshapiro } 27964562Sgshapiro if (bitset(SFF_NOWWFILES, flags) && 28064562Sgshapiro bitset(S_IWOTH, st->st_mode)) 28164562Sgshapiro { 28264562Sgshapiro if (tTd(44, 4)) 28390792Sgshapiro sm_dprintf("\t[write bits %lo]\tE_SM_WWFILE\n", 28490792Sgshapiro (unsigned long) st->st_mode); 28564562Sgshapiro return E_SM_WWFILE; 28664562Sgshapiro } 28764562Sgshapiro if (bitset(SFF_NOGRFILES, flags) && bitset(S_IRGRP, st->st_mode)) 28864562Sgshapiro { 28964562Sgshapiro if (tTd(44, 4)) 29090792Sgshapiro sm_dprintf("\t[read bits %lo]\tE_SM_GRFILE\n", 29190792Sgshapiro (unsigned long) st->st_mode); 29264562Sgshapiro return E_SM_GRFILE; 29364562Sgshapiro } 29464562Sgshapiro if (bitset(SFF_NOWRFILES, flags) && bitset(S_IROTH, st->st_mode)) 29564562Sgshapiro { 29664562Sgshapiro if (tTd(44, 4)) 29790792Sgshapiro sm_dprintf("\t[read bits %lo]\tE_SM_WRFILE\n", 29890792Sgshapiro (unsigned long) st->st_mode); 29964562Sgshapiro return E_SM_WRFILE; 30064562Sgshapiro } 30164562Sgshapiro if (!bitset(SFF_EXECOK, flags) && 30264562Sgshapiro bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) && 30364562Sgshapiro bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode)) 30464562Sgshapiro { 30564562Sgshapiro if (tTd(44, 4)) 306132943Sgshapiro sm_dprintf("\t[exec bits %lo]\tE_SM_ISEXEC\n", 30790792Sgshapiro (unsigned long) st->st_mode); 30864562Sgshapiro return E_SM_ISEXEC; 30964562Sgshapiro } 31064562Sgshapiro if (bitset(SFF_NOHLINK, flags) && st->st_nlink != 1) 31164562Sgshapiro { 31264562Sgshapiro if (tTd(44, 4)) 31390792Sgshapiro sm_dprintf("\t[link count %d]\tE_SM_NOHLINK\n", 31464562Sgshapiro (int) st->st_nlink); 31564562Sgshapiro return E_SM_NOHLINK; 31664562Sgshapiro } 31764562Sgshapiro 31864562Sgshapiro if (uid == 0 && bitset(SFF_OPENASROOT, flags)) 31964562Sgshapiro /* EMPTY */ 32064562Sgshapiro ; 32164562Sgshapiro else if (uid == 0 && !bitset(SFF_ROOTOK, flags)) 32264562Sgshapiro mode >>= 6; 32364562Sgshapiro else if (st->st_uid == uid) 32464562Sgshapiro /* EMPTY */ 32564562Sgshapiro ; 32664562Sgshapiro else if (uid == 0 && st->st_uid == TrustedUid) 32764562Sgshapiro /* EMPTY */ 32864562Sgshapiro ; 32964562Sgshapiro else 33064562Sgshapiro { 33164562Sgshapiro mode >>= 3; 33264562Sgshapiro if (st->st_gid == gid) 33364562Sgshapiro /* EMPTY */ 33464562Sgshapiro ; 33564562Sgshapiro# ifndef NO_GROUP_SET 33664562Sgshapiro else if (user != NULL && !DontInitGroups && 33764562Sgshapiro ((gr != NULL && gr->gr_gid == st->st_gid) || 33864562Sgshapiro (gr = getgrgid(st->st_gid)) != NULL)) 33964562Sgshapiro { 34064562Sgshapiro register char **gp; 34164562Sgshapiro 34264562Sgshapiro for (gp = gr->gr_mem; *gp != NULL; gp++) 34364562Sgshapiro if (strcmp(*gp, user) == 0) 34464562Sgshapiro break; 34564562Sgshapiro if (*gp == NULL) 34664562Sgshapiro mode >>= 3; 34764562Sgshapiro } 34864562Sgshapiro# endif /* ! NO_GROUP_SET */ 34964562Sgshapiro else 35064562Sgshapiro mode >>= 3; 35164562Sgshapiro } 35264562Sgshapiro if (tTd(44, 4)) 35390792Sgshapiro sm_dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ", 35464562Sgshapiro (int) st->st_uid, (int) st->st_nlink, 35590792Sgshapiro (unsigned long) st->st_mode, (unsigned long) mode); 35664562Sgshapiro if ((st->st_uid == uid || st->st_uid == 0 || 35764562Sgshapiro st->st_uid == TrustedUid || 35864562Sgshapiro !bitset(SFF_MUSTOWN, flags)) && 35964562Sgshapiro (st->st_mode & mode) == mode) 36064562Sgshapiro { 36164562Sgshapiro if (tTd(44, 4)) 36290792Sgshapiro sm_dprintf("\tOK\n"); 36364562Sgshapiro return 0; 36464562Sgshapiro } 36564562Sgshapiro if (tTd(44, 4)) 36690792Sgshapiro sm_dprintf("\tEACCES\n"); 36764562Sgshapiro return EACCES; 36864562Sgshapiro} 36990792Sgshapiro/* 37064562Sgshapiro** SAFEDIRPATH -- check to make sure a path to a directory is safe 37164562Sgshapiro** 37264562Sgshapiro** Safe means not writable and owned by the right folks. 37364562Sgshapiro** 37464562Sgshapiro** Parameters: 37564562Sgshapiro** fn -- filename to check. 37664562Sgshapiro** uid -- user id to compare against. 37764562Sgshapiro** gid -- group id to compare against. 37864562Sgshapiro** user -- user name to compare against (used for group 37964562Sgshapiro** sets). 38064562Sgshapiro** flags -- modifiers: 38164562Sgshapiro** SFF_ROOTOK -- ok to use root permissions to open. 38264562Sgshapiro** SFF_SAFEDIRPATH -- writable directories are considered 38364562Sgshapiro** to be fatal errors. 38464562Sgshapiro** level -- symlink recursive level. 38564562Sgshapiro** offset -- offset into fn to start checking from. 38664562Sgshapiro** 38764562Sgshapiro** Returns: 38864562Sgshapiro** 0 -- if the directory path is "safe". 38964562Sgshapiro** else -- an error number associated with the path. 39064562Sgshapiro*/ 39164562Sgshapiro 39264562Sgshapiroint 39364562Sgshapirosafedirpath(fn, uid, gid, user, flags, level, offset) 39464562Sgshapiro char *fn; 39564562Sgshapiro UID_T uid; 39664562Sgshapiro GID_T gid; 39764562Sgshapiro char *user; 39864562Sgshapiro long flags; 39964562Sgshapiro int level; 40064562Sgshapiro int offset; 40164562Sgshapiro{ 40264562Sgshapiro int ret = 0; 40364562Sgshapiro int mode = S_IWOTH; 40464562Sgshapiro char save = '\0'; 40564562Sgshapiro char *saveptr = NULL; 40664562Sgshapiro char *p, *enddir; 40764562Sgshapiro register struct group *gr = NULL; 40898121Sgshapiro char s[MAXLINKPATHLEN]; 40964562Sgshapiro struct stat stbuf; 41064562Sgshapiro 41164562Sgshapiro /* make sure we aren't in a symlink loop */ 41264562Sgshapiro if (level > MAXSYMLINKS) 41364562Sgshapiro return ELOOP; 41464562Sgshapiro 41590792Sgshapiro if (level < 0 || offset < 0 || offset > strlen(fn)) 41690792Sgshapiro return EINVAL; 41790792Sgshapiro 41864562Sgshapiro /* special case root directory */ 41964562Sgshapiro if (*fn == '\0') 42064562Sgshapiro fn = "/"; 42164562Sgshapiro 42264562Sgshapiro if (tTd(44, 4)) 42390792Sgshapiro sm_dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n", 42464562Sgshapiro fn, (long) uid, (long) gid, flags, level, offset); 42564562Sgshapiro 42664562Sgshapiro if (!bitnset(DBS_GROUPWRITABLEDIRPATHSAFE, DontBlameSendmail)) 42764562Sgshapiro mode |= S_IWGRP; 42864562Sgshapiro 42964562Sgshapiro /* Make a modifiable copy of the filename */ 43090792Sgshapiro if (sm_strlcpy(s, fn, sizeof s) >= sizeof s) 43164562Sgshapiro return EINVAL; 43264562Sgshapiro 43364562Sgshapiro p = s + offset; 43464562Sgshapiro while (p != NULL) 43564562Sgshapiro { 43664562Sgshapiro /* put back character */ 43764562Sgshapiro if (saveptr != NULL) 43864562Sgshapiro { 43964562Sgshapiro *saveptr = save; 44064562Sgshapiro saveptr = NULL; 44164562Sgshapiro p++; 44264562Sgshapiro } 44364562Sgshapiro 44464562Sgshapiro if (*p == '\0') 44564562Sgshapiro break; 44664562Sgshapiro 44764562Sgshapiro p = strchr(p, '/'); 44864562Sgshapiro 44964562Sgshapiro /* Special case for root directory */ 45064562Sgshapiro if (p == s) 45164562Sgshapiro { 45264562Sgshapiro save = *(p + 1); 45364562Sgshapiro saveptr = p + 1; 45464562Sgshapiro *(p + 1) = '\0'; 45564562Sgshapiro } 45664562Sgshapiro else if (p != NULL) 45764562Sgshapiro { 45864562Sgshapiro save = *p; 45964562Sgshapiro saveptr = p; 46064562Sgshapiro *p = '\0'; 46164562Sgshapiro } 46264562Sgshapiro 46364562Sgshapiro /* Heuristic: . and .. have already been checked */ 46464562Sgshapiro enddir = strrchr(s, '/'); 46564562Sgshapiro if (enddir != NULL && 46664562Sgshapiro (strcmp(enddir, "/..") == 0 || 46764562Sgshapiro strcmp(enddir, "/.") == 0)) 46864562Sgshapiro continue; 46964562Sgshapiro 47064562Sgshapiro if (tTd(44, 20)) 47190792Sgshapiro sm_dprintf("\t[dir %s]\n", s); 47264562Sgshapiro 47364562Sgshapiro# if HASLSTAT 47464562Sgshapiro ret = lstat(s, &stbuf); 475363466Sgshapiro# else 47664562Sgshapiro ret = stat(s, &stbuf); 477363466Sgshapiro# endif 47864562Sgshapiro if (ret < 0) 47964562Sgshapiro { 48064562Sgshapiro ret = errno; 48164562Sgshapiro break; 48264562Sgshapiro } 48364562Sgshapiro 48464562Sgshapiro# ifdef S_ISLNK 48564562Sgshapiro /* Follow symlinks */ 48664562Sgshapiro if (S_ISLNK(stbuf.st_mode)) 48764562Sgshapiro { 48898121Sgshapiro int linklen; 48964562Sgshapiro char *target; 49098121Sgshapiro char buf[MAXPATHLEN]; 491141858Sgshapiro char fullbuf[MAXLINKPATHLEN]; 49264562Sgshapiro 49364562Sgshapiro memset(buf, '\0', sizeof buf); 49498121Sgshapiro linklen = readlink(s, buf, sizeof buf); 49598121Sgshapiro if (linklen < 0) 49664562Sgshapiro { 49764562Sgshapiro ret = errno; 49864562Sgshapiro break; 49964562Sgshapiro } 50098121Sgshapiro if (linklen >= sizeof buf) 50198121Sgshapiro { 50298121Sgshapiro /* file name too long for buffer */ 50398121Sgshapiro ret = errno = EINVAL; 50498121Sgshapiro break; 50598121Sgshapiro } 50664562Sgshapiro 50764562Sgshapiro offset = 0; 50864562Sgshapiro if (*buf == '/') 50964562Sgshapiro { 51064562Sgshapiro target = buf; 51164562Sgshapiro 51264562Sgshapiro /* If path is the same, avoid rechecks */ 51364562Sgshapiro while (s[offset] == buf[offset] && 51464562Sgshapiro s[offset] != '\0') 51564562Sgshapiro offset++; 51664562Sgshapiro 51764562Sgshapiro if (s[offset] == '\0' && buf[offset] == '\0') 51864562Sgshapiro { 51964562Sgshapiro /* strings match, symlink loop */ 52064562Sgshapiro return ELOOP; 52164562Sgshapiro } 52264562Sgshapiro 52364562Sgshapiro /* back off from the mismatch */ 52464562Sgshapiro if (offset > 0) 52564562Sgshapiro offset--; 52664562Sgshapiro 52764562Sgshapiro /* Make sure we are at a directory break */ 52864562Sgshapiro if (offset > 0 && 52964562Sgshapiro s[offset] != '/' && 53064562Sgshapiro s[offset] != '\0') 53164562Sgshapiro { 53264562Sgshapiro while (buf[offset] != '/' && 53364562Sgshapiro offset > 0) 53464562Sgshapiro offset--; 53564562Sgshapiro } 53664562Sgshapiro if (offset > 0 && 53764562Sgshapiro s[offset] == '/' && 53864562Sgshapiro buf[offset] == '/') 53964562Sgshapiro { 54064562Sgshapiro /* Include the trailing slash */ 54164562Sgshapiro offset++; 54264562Sgshapiro } 54364562Sgshapiro } 54464562Sgshapiro else 54564562Sgshapiro { 54664562Sgshapiro char *sptr; 54764562Sgshapiro 54864562Sgshapiro sptr = strrchr(s, '/'); 54964562Sgshapiro if (sptr != NULL) 55064562Sgshapiro { 55164562Sgshapiro *sptr = '\0'; 55264562Sgshapiro offset = sptr + 1 - s; 55390792Sgshapiro if (sm_strlcpyn(fullbuf, 55490792Sgshapiro sizeof fullbuf, 2, 55590792Sgshapiro s, "/") >= 55690792Sgshapiro sizeof fullbuf || 55790792Sgshapiro sm_strlcat(fullbuf, buf, 55890792Sgshapiro sizeof fullbuf) >= 55990792Sgshapiro sizeof fullbuf) 56064562Sgshapiro { 56164562Sgshapiro ret = EINVAL; 56264562Sgshapiro break; 56364562Sgshapiro } 56464562Sgshapiro *sptr = '/'; 56564562Sgshapiro } 56664562Sgshapiro else 56764562Sgshapiro { 56890792Sgshapiro if (sm_strlcpy(fullbuf, buf, 56990792Sgshapiro sizeof fullbuf) >= 57090792Sgshapiro sizeof fullbuf) 57164562Sgshapiro { 57264562Sgshapiro ret = EINVAL; 57364562Sgshapiro break; 57464562Sgshapiro } 57564562Sgshapiro } 57664562Sgshapiro target = fullbuf; 57764562Sgshapiro } 57864562Sgshapiro ret = safedirpath(target, uid, gid, user, flags, 57964562Sgshapiro level + 1, offset); 58064562Sgshapiro if (ret != 0) 58164562Sgshapiro break; 58264562Sgshapiro 58364562Sgshapiro /* Don't check permissions on the link file itself */ 58464562Sgshapiro continue; 58564562Sgshapiro } 58664562Sgshapiro#endif /* S_ISLNK */ 58764562Sgshapiro 58864562Sgshapiro if ((uid == 0 || bitset(SFF_SAFEDIRPATH, flags)) && 58964562Sgshapiro#ifdef S_ISVTX 59064562Sgshapiro !(bitnset(DBS_TRUSTSTICKYBIT, DontBlameSendmail) && 59164562Sgshapiro bitset(S_ISVTX, stbuf.st_mode)) && 592363466Sgshapiro#endif 59364562Sgshapiro bitset(mode, stbuf.st_mode)) 59464562Sgshapiro { 59564562Sgshapiro if (tTd(44, 4)) 59690792Sgshapiro sm_dprintf("\t[dir %s] mode %lo ", 59790792Sgshapiro s, (unsigned long) stbuf.st_mode); 59864562Sgshapiro if (bitset(SFF_SAFEDIRPATH, flags)) 59964562Sgshapiro { 60064562Sgshapiro if (bitset(S_IWOTH, stbuf.st_mode)) 60164562Sgshapiro ret = E_SM_WWDIR; 60264562Sgshapiro else 60364562Sgshapiro ret = E_SM_GWDIR; 60464562Sgshapiro if (tTd(44, 4)) 60590792Sgshapiro sm_dprintf("FATAL\n"); 60664562Sgshapiro break; 60764562Sgshapiro } 60864562Sgshapiro if (tTd(44, 4)) 60990792Sgshapiro sm_dprintf("WARNING\n"); 61064562Sgshapiro if (Verbose > 1) 61164562Sgshapiro message("051 WARNING: %s writable directory %s", 61264562Sgshapiro bitset(S_IWOTH, stbuf.st_mode) 61364562Sgshapiro ? "World" 61464562Sgshapiro : "Group", 61564562Sgshapiro s); 61664562Sgshapiro } 61764562Sgshapiro if (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags)) 61864562Sgshapiro { 61964562Sgshapiro if (bitset(S_IXOTH, stbuf.st_mode)) 62064562Sgshapiro continue; 62164562Sgshapiro ret = EACCES; 62264562Sgshapiro break; 62364562Sgshapiro } 62464562Sgshapiro 62564562Sgshapiro /* 62664562Sgshapiro ** Let OS determine access to file if we are not 62764562Sgshapiro ** running as a privileged user. This allows ACLs 62864562Sgshapiro ** to work. Also, if opening as root, assume we can 62964562Sgshapiro ** scan the directory. 63064562Sgshapiro */ 63164562Sgshapiro if (geteuid() != 0 || bitset(SFF_OPENASROOT, flags)) 63264562Sgshapiro continue; 63364562Sgshapiro 63464562Sgshapiro if (stbuf.st_uid == uid && 63564562Sgshapiro bitset(S_IXUSR, stbuf.st_mode)) 63664562Sgshapiro continue; 63764562Sgshapiro if (stbuf.st_gid == gid && 63864562Sgshapiro bitset(S_IXGRP, stbuf.st_mode)) 63964562Sgshapiro continue; 64064562Sgshapiro# ifndef NO_GROUP_SET 64164562Sgshapiro if (user != NULL && !DontInitGroups && 64264562Sgshapiro ((gr != NULL && gr->gr_gid == stbuf.st_gid) || 64364562Sgshapiro (gr = getgrgid(stbuf.st_gid)) != NULL)) 64464562Sgshapiro { 64564562Sgshapiro register char **gp; 64664562Sgshapiro 64764562Sgshapiro for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++) 64864562Sgshapiro if (strcmp(*gp, user) == 0) 64964562Sgshapiro break; 65064562Sgshapiro if (gp != NULL && *gp != NULL && 65164562Sgshapiro bitset(S_IXGRP, stbuf.st_mode)) 65264562Sgshapiro continue; 65364562Sgshapiro } 65464562Sgshapiro# endif /* ! NO_GROUP_SET */ 65564562Sgshapiro if (!bitset(S_IXOTH, stbuf.st_mode)) 65664562Sgshapiro { 65764562Sgshapiro ret = EACCES; 65864562Sgshapiro break; 65964562Sgshapiro } 66064562Sgshapiro } 66164562Sgshapiro if (tTd(44, 4)) 66290792Sgshapiro sm_dprintf("\t[dir %s] %s\n", fn, 66390792Sgshapiro ret == 0 ? "OK" : sm_errstring(ret)); 66464562Sgshapiro return ret; 66564562Sgshapiro} 66690792Sgshapiro/* 66764562Sgshapiro** SAFEOPEN -- do a file open with extra checking 66864562Sgshapiro** 66964562Sgshapiro** Parameters: 67064562Sgshapiro** fn -- the file name to open. 67164562Sgshapiro** omode -- the open-style mode flags. 67264562Sgshapiro** cmode -- the create-style mode flags. 67364562Sgshapiro** sff -- safefile flags. 67464562Sgshapiro** 67564562Sgshapiro** Returns: 67664562Sgshapiro** Same as open. 67764562Sgshapiro*/ 67864562Sgshapiro 67964562Sgshapiroint 68064562Sgshapirosafeopen(fn, omode, cmode, sff) 68164562Sgshapiro char *fn; 68264562Sgshapiro int omode; 68364562Sgshapiro int cmode; 68464562Sgshapiro long sff; 68564562Sgshapiro{ 686132943Sgshapiro#if !NOFTRUNCATE 687132943Sgshapiro bool truncate; 688363466Sgshapiro#endif 68964562Sgshapiro int rval; 69064562Sgshapiro int fd; 69164562Sgshapiro int smode; 69264562Sgshapiro struct stat stb; 69364562Sgshapiro 69464562Sgshapiro if (tTd(44, 10)) 69590792Sgshapiro sm_dprintf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n", 69690792Sgshapiro fn, omode, cmode, sff); 69764562Sgshapiro 69864562Sgshapiro if (bitset(O_CREAT, omode)) 69964562Sgshapiro sff |= SFF_CREAT; 70064562Sgshapiro omode &= ~O_CREAT; 70164562Sgshapiro switch (omode & O_ACCMODE) 70264562Sgshapiro { 70364562Sgshapiro case O_RDONLY: 70464562Sgshapiro smode = S_IREAD; 70564562Sgshapiro break; 70664562Sgshapiro 70764562Sgshapiro case O_WRONLY: 70864562Sgshapiro smode = S_IWRITE; 70964562Sgshapiro break; 71064562Sgshapiro 71164562Sgshapiro case O_RDWR: 71264562Sgshapiro smode = S_IREAD|S_IWRITE; 71364562Sgshapiro break; 71464562Sgshapiro 71564562Sgshapiro default: 71664562Sgshapiro smode = 0; 71764562Sgshapiro break; 71864562Sgshapiro } 71964562Sgshapiro if (bitset(SFF_OPENASROOT, sff)) 72064562Sgshapiro rval = safefile(fn, RunAsUid, RunAsGid, RunAsUserName, 72164562Sgshapiro sff, smode, &stb); 72264562Sgshapiro else 72364562Sgshapiro rval = safefile(fn, RealUid, RealGid, RealUserName, 72464562Sgshapiro sff, smode, &stb); 72564562Sgshapiro if (rval != 0) 72664562Sgshapiro { 72764562Sgshapiro errno = rval; 72864562Sgshapiro return -1; 72964562Sgshapiro } 73064562Sgshapiro if (stb.st_mode == ST_MODE_NOFILE && bitset(SFF_CREAT, sff)) 73164562Sgshapiro omode |= O_CREAT | (bitset(SFF_NOTEXCL, sff) ? 0 : O_EXCL); 73264562Sgshapiro else if (bitset(SFF_CREAT, sff) && bitset(O_EXCL, omode)) 73364562Sgshapiro { 73464562Sgshapiro /* The file exists so an exclusive create would fail */ 73564562Sgshapiro errno = EEXIST; 73664562Sgshapiro return -1; 73764562Sgshapiro } 73864562Sgshapiro 739132943Sgshapiro#if !NOFTRUNCATE 740132943Sgshapiro truncate = bitset(O_TRUNC, omode); 741132943Sgshapiro if (truncate) 742132943Sgshapiro omode &= ~O_TRUNC; 743363466Sgshapiro#endif 744132943Sgshapiro 74564562Sgshapiro fd = dfopen(fn, omode, cmode, sff); 74664562Sgshapiro if (fd < 0) 74764562Sgshapiro return fd; 74864562Sgshapiro if (filechanged(fn, fd, &stb)) 74964562Sgshapiro { 75064562Sgshapiro syserr("554 5.3.0 cannot open: file %s changed after open", fn); 75164562Sgshapiro (void) close(fd); 75264562Sgshapiro errno = E_SM_FILECHANGE; 75364562Sgshapiro return -1; 75464562Sgshapiro } 755132943Sgshapiro 756132943Sgshapiro#if !NOFTRUNCATE 757132943Sgshapiro if (truncate && 758132943Sgshapiro ftruncate(fd, (off_t) 0) < 0) 759132943Sgshapiro { 760132943Sgshapiro int save_errno; 761132943Sgshapiro 762132943Sgshapiro save_errno = errno; 763132943Sgshapiro syserr("554 5.3.0 cannot open: file %s could not be truncated", 764132943Sgshapiro fn); 765132943Sgshapiro (void) close(fd); 766132943Sgshapiro errno = save_errno; 767132943Sgshapiro return -1; 768132943Sgshapiro } 769132943Sgshapiro#endif /* !NOFTRUNCATE */ 770132943Sgshapiro 77164562Sgshapiro return fd; 77264562Sgshapiro} 77390792Sgshapiro/* 77490792Sgshapiro** SAFEFOPEN -- do a file open with extra checking 77590792Sgshapiro** 77690792Sgshapiro** Parameters: 77790792Sgshapiro** fn -- the file name to open. 77890792Sgshapiro** omode -- the open-style mode flags. 77990792Sgshapiro** cmode -- the create-style mode flags. 78090792Sgshapiro** sff -- safefile flags. 78190792Sgshapiro** 78290792Sgshapiro** Returns: 78390792Sgshapiro** Same as fopen. 78490792Sgshapiro*/ 78590792Sgshapiro 78690792SgshapiroSM_FILE_T * 78790792Sgshapirosafefopen(fn, omode, cmode, sff) 78890792Sgshapiro char *fn; 78990792Sgshapiro int omode; 79090792Sgshapiro int cmode; 79190792Sgshapiro long sff; 79290792Sgshapiro{ 79390792Sgshapiro int fd; 79490792Sgshapiro int save_errno; 79590792Sgshapiro SM_FILE_T *fp; 79690792Sgshapiro int fmode; 79790792Sgshapiro 79890792Sgshapiro switch (omode & O_ACCMODE) 79990792Sgshapiro { 80090792Sgshapiro case O_RDONLY: 80190792Sgshapiro fmode = SM_IO_RDONLY; 80290792Sgshapiro break; 80390792Sgshapiro 80490792Sgshapiro case O_WRONLY: 80590792Sgshapiro if (bitset(O_APPEND, omode)) 80690792Sgshapiro fmode = SM_IO_APPEND; 80790792Sgshapiro else 80890792Sgshapiro fmode = SM_IO_WRONLY; 80990792Sgshapiro break; 81090792Sgshapiro 81190792Sgshapiro case O_RDWR: 81290792Sgshapiro if (bitset(O_TRUNC, omode)) 81390792Sgshapiro fmode = SM_IO_RDWRTR; 81490792Sgshapiro else if (bitset(O_APPEND, omode)) 81590792Sgshapiro fmode = SM_IO_APPENDRW; 81690792Sgshapiro else 81790792Sgshapiro fmode = SM_IO_RDWR; 81890792Sgshapiro break; 81990792Sgshapiro 82090792Sgshapiro default: 82190792Sgshapiro syserr("554 5.3.5 safefopen: unknown omode %o", omode); 82290792Sgshapiro fmode = 0; 82390792Sgshapiro } 82490792Sgshapiro fd = safeopen(fn, omode, cmode, sff); 82590792Sgshapiro if (fd < 0) 82690792Sgshapiro { 82790792Sgshapiro save_errno = errno; 82890792Sgshapiro if (tTd(44, 10)) 82990792Sgshapiro sm_dprintf("safefopen: safeopen failed: %s\n", 83090792Sgshapiro sm_errstring(errno)); 83190792Sgshapiro errno = save_errno; 83290792Sgshapiro return NULL; 83390792Sgshapiro } 83490792Sgshapiro fp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, 83590792Sgshapiro (void *) &fd, fmode, NULL); 83690792Sgshapiro if (fp != NULL) 83790792Sgshapiro return fp; 83890792Sgshapiro 83990792Sgshapiro save_errno = errno; 84090792Sgshapiro if (tTd(44, 10)) 84190792Sgshapiro { 84290792Sgshapiro sm_dprintf("safefopen: fdopen(%s, %d) failed: omode=%x, sff=%lx, err=%s\n", 84390792Sgshapiro fn, fmode, omode, sff, sm_errstring(errno)); 84490792Sgshapiro } 84590792Sgshapiro (void) close(fd); 84690792Sgshapiro errno = save_errno; 84790792Sgshapiro return NULL; 84890792Sgshapiro} 84990792Sgshapiro/* 85064562Sgshapiro** FILECHANGED -- check to see if file changed after being opened 85164562Sgshapiro** 85264562Sgshapiro** Parameters: 85364562Sgshapiro** fn -- pathname of file to check. 85464562Sgshapiro** fd -- file descriptor to check. 85564562Sgshapiro** stb -- stat structure from before open. 85664562Sgshapiro** 85764562Sgshapiro** Returns: 85890792Sgshapiro** true -- if a problem was detected. 85990792Sgshapiro** false -- if this file is still the same. 86064562Sgshapiro*/ 86164562Sgshapiro 86264562Sgshapirobool 86364562Sgshapirofilechanged(fn, fd, stb) 86464562Sgshapiro char *fn; 86564562Sgshapiro int fd; 86664562Sgshapiro struct stat *stb; 86764562Sgshapiro{ 86864562Sgshapiro struct stat sta; 86964562Sgshapiro 87064562Sgshapiro if (stb->st_mode == ST_MODE_NOFILE) 87164562Sgshapiro { 87264562Sgshapiro# if HASLSTAT && BOGUS_O_EXCL 87364562Sgshapiro /* only necessary if exclusive open follows symbolic links */ 87464562Sgshapiro if (lstat(fn, stb) < 0 || stb->st_nlink != 1) 87590792Sgshapiro return true; 876363466Sgshapiro# else 87790792Sgshapiro return false; 878363466Sgshapiro# endif 87964562Sgshapiro } 88064562Sgshapiro if (fstat(fd, &sta) < 0) 88190792Sgshapiro return true; 88264562Sgshapiro 88364562Sgshapiro if (sta.st_nlink != stb->st_nlink || 88464562Sgshapiro sta.st_dev != stb->st_dev || 88564562Sgshapiro sta.st_ino != stb->st_ino || 88664562Sgshapiro# if HAS_ST_GEN && 0 /* AFS returns garbage in st_gen */ 88764562Sgshapiro sta.st_gen != stb->st_gen || 888363466Sgshapiro# endif 88964562Sgshapiro sta.st_uid != stb->st_uid || 89064562Sgshapiro sta.st_gid != stb->st_gid) 89164562Sgshapiro { 89264562Sgshapiro if (tTd(44, 8)) 89364562Sgshapiro { 89490792Sgshapiro sm_dprintf("File changed after opening:\n"); 89590792Sgshapiro sm_dprintf(" nlink = %ld/%ld\n", 89664562Sgshapiro (long) stb->st_nlink, (long) sta.st_nlink); 89790792Sgshapiro sm_dprintf(" dev = %ld/%ld\n", 89864562Sgshapiro (long) stb->st_dev, (long) sta.st_dev); 89990792Sgshapiro sm_dprintf(" ino = %llu/%llu\n", 90090792Sgshapiro (ULONGLONG_T) stb->st_ino, 90190792Sgshapiro (ULONGLONG_T) sta.st_ino); 90264562Sgshapiro# if HAS_ST_GEN 90390792Sgshapiro sm_dprintf(" gen = %ld/%ld\n", 90464562Sgshapiro (long) stb->st_gen, (long) sta.st_gen); 905363466Sgshapiro# endif 90690792Sgshapiro sm_dprintf(" uid = %ld/%ld\n", 90764562Sgshapiro (long) stb->st_uid, (long) sta.st_uid); 90890792Sgshapiro sm_dprintf(" gid = %ld/%ld\n", 90964562Sgshapiro (long) stb->st_gid, (long) sta.st_gid); 91064562Sgshapiro } 91190792Sgshapiro return true; 91264562Sgshapiro } 91364562Sgshapiro 91490792Sgshapiro return false; 91564562Sgshapiro} 91690792Sgshapiro/* 91764562Sgshapiro** DFOPEN -- determined file open 91864562Sgshapiro** 91964562Sgshapiro** This routine has the semantics of open, except that it will 92064562Sgshapiro** keep trying a few times to make this happen. The idea is that 92164562Sgshapiro** on very loaded systems, we may run out of resources (inodes, 92264562Sgshapiro** whatever), so this tries to get around it. 92364562Sgshapiro*/ 92464562Sgshapiro 92564562Sgshapiroint 92664562Sgshapirodfopen(filename, omode, cmode, sff) 92764562Sgshapiro char *filename; 92864562Sgshapiro int omode; 92964562Sgshapiro int cmode; 93064562Sgshapiro long sff; 93164562Sgshapiro{ 93264562Sgshapiro register int tries; 93364562Sgshapiro int fd = -1; 93464562Sgshapiro struct stat st; 93564562Sgshapiro 93664562Sgshapiro for (tries = 0; tries < 10; tries++) 93764562Sgshapiro { 93864562Sgshapiro (void) sleep((unsigned) (10 * tries)); 93964562Sgshapiro errno = 0; 94064562Sgshapiro fd = open(filename, omode, cmode); 94164562Sgshapiro if (fd >= 0) 94264562Sgshapiro break; 94364562Sgshapiro switch (errno) 94464562Sgshapiro { 94564562Sgshapiro case ENFILE: /* system file table full */ 94664562Sgshapiro case EINTR: /* interrupted syscall */ 94764562Sgshapiro#ifdef ETXTBSY 94864562Sgshapiro case ETXTBSY: /* Apollo: net file locked */ 949363466Sgshapiro#endif 95064562Sgshapiro continue; 95164562Sgshapiro } 95264562Sgshapiro break; 95364562Sgshapiro } 95464562Sgshapiro if (!bitset(SFF_NOLOCK, sff) && 95564562Sgshapiro fd >= 0 && 95664562Sgshapiro fstat(fd, &st) >= 0 && 95764562Sgshapiro S_ISREG(st.st_mode)) 95864562Sgshapiro { 95964562Sgshapiro int locktype; 96064562Sgshapiro 96164562Sgshapiro /* lock the file to avoid accidental conflicts */ 96264562Sgshapiro if ((omode & O_ACCMODE) != O_RDONLY) 96364562Sgshapiro locktype = LOCK_EX; 96464562Sgshapiro else 96564562Sgshapiro locktype = LOCK_SH; 966132943Sgshapiro if (bitset(SFF_NBLOCK, sff)) 967132943Sgshapiro locktype |= LOCK_NB; 968132943Sgshapiro 96964562Sgshapiro if (!lockfile(fd, filename, NULL, locktype)) 97064562Sgshapiro { 97164562Sgshapiro int save_errno = errno; 97264562Sgshapiro 97364562Sgshapiro (void) close(fd); 97464562Sgshapiro fd = -1; 97564562Sgshapiro errno = save_errno; 97664562Sgshapiro } 97764562Sgshapiro else 97864562Sgshapiro errno = 0; 97964562Sgshapiro } 98064562Sgshapiro return fd; 98164562Sgshapiro} 982