1*7c478bd9Sstevel@tonic-gate /* 2*7c478bd9Sstevel@tonic-gate * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers. 3*7c478bd9Sstevel@tonic-gate * All rights reserved. 4*7c478bd9Sstevel@tonic-gate * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. 5*7c478bd9Sstevel@tonic-gate * Copyright (c) 1988, 1993 6*7c478bd9Sstevel@tonic-gate * The Regents of the University of California. All rights reserved. 7*7c478bd9Sstevel@tonic-gate * 8*7c478bd9Sstevel@tonic-gate * By using this file, you agree to the terms and conditions set 9*7c478bd9Sstevel@tonic-gate * forth in the LICENSE file which can be found at the top level of 10*7c478bd9Sstevel@tonic-gate * the sendmail distribution. 11*7c478bd9Sstevel@tonic-gate * 12*7c478bd9Sstevel@tonic-gate */ 13*7c478bd9Sstevel@tonic-gate 14*7c478bd9Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI" 15*7c478bd9Sstevel@tonic-gate 16*7c478bd9Sstevel@tonic-gate #include <sendmail.h> 17*7c478bd9Sstevel@tonic-gate #include <sm/io.h> 18*7c478bd9Sstevel@tonic-gate #include <sm/errstring.h> 19*7c478bd9Sstevel@tonic-gate 20*7c478bd9Sstevel@tonic-gate SM_RCSID("@(#)$Id: safefile.c,v 8.128 2004/09/30 18:15:49 ca Exp $") 21*7c478bd9Sstevel@tonic-gate 22*7c478bd9Sstevel@tonic-gate 23*7c478bd9Sstevel@tonic-gate /* 24*7c478bd9Sstevel@tonic-gate ** SAFEFILE -- return 0 if a file exists and is safe for a user. 25*7c478bd9Sstevel@tonic-gate ** 26*7c478bd9Sstevel@tonic-gate ** Parameters: 27*7c478bd9Sstevel@tonic-gate ** fn -- filename to check. 28*7c478bd9Sstevel@tonic-gate ** uid -- user id to compare against. 29*7c478bd9Sstevel@tonic-gate ** gid -- group id to compare against. 30*7c478bd9Sstevel@tonic-gate ** user -- user name to compare against (used for group 31*7c478bd9Sstevel@tonic-gate ** sets). 32*7c478bd9Sstevel@tonic-gate ** flags -- modifiers: 33*7c478bd9Sstevel@tonic-gate ** SFF_MUSTOWN -- "uid" must own this file. 34*7c478bd9Sstevel@tonic-gate ** SFF_NOSLINK -- file cannot be a symbolic link. 35*7c478bd9Sstevel@tonic-gate ** mode -- mode bits that must match. 36*7c478bd9Sstevel@tonic-gate ** st -- if set, points to a stat structure that will 37*7c478bd9Sstevel@tonic-gate ** get the stat info for the file. 38*7c478bd9Sstevel@tonic-gate ** 39*7c478bd9Sstevel@tonic-gate ** Returns: 40*7c478bd9Sstevel@tonic-gate ** 0 if fn exists, is owned by uid, and matches mode. 41*7c478bd9Sstevel@tonic-gate ** An errno otherwise. The actual errno is cleared. 42*7c478bd9Sstevel@tonic-gate ** 43*7c478bd9Sstevel@tonic-gate ** Side Effects: 44*7c478bd9Sstevel@tonic-gate ** none. 45*7c478bd9Sstevel@tonic-gate */ 46*7c478bd9Sstevel@tonic-gate 47*7c478bd9Sstevel@tonic-gate int 48*7c478bd9Sstevel@tonic-gate safefile(fn, uid, gid, user, flags, mode, st) 49*7c478bd9Sstevel@tonic-gate char *fn; 50*7c478bd9Sstevel@tonic-gate UID_T uid; 51*7c478bd9Sstevel@tonic-gate GID_T gid; 52*7c478bd9Sstevel@tonic-gate char *user; 53*7c478bd9Sstevel@tonic-gate long flags; 54*7c478bd9Sstevel@tonic-gate int mode; 55*7c478bd9Sstevel@tonic-gate struct stat *st; 56*7c478bd9Sstevel@tonic-gate { 57*7c478bd9Sstevel@tonic-gate register char *p; 58*7c478bd9Sstevel@tonic-gate register struct group *gr = NULL; 59*7c478bd9Sstevel@tonic-gate int file_errno = 0; 60*7c478bd9Sstevel@tonic-gate bool checkpath; 61*7c478bd9Sstevel@tonic-gate struct stat stbuf; 62*7c478bd9Sstevel@tonic-gate struct stat fstbuf; 63*7c478bd9Sstevel@tonic-gate char fbuf[MAXPATHLEN]; 64*7c478bd9Sstevel@tonic-gate 65*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 66*7c478bd9Sstevel@tonic-gate sm_dprintf("safefile(%s, uid=%d, gid=%d, flags=%lx, mode=%o):\n", 67*7c478bd9Sstevel@tonic-gate fn, (int) uid, (int) gid, flags, mode); 68*7c478bd9Sstevel@tonic-gate errno = 0; 69*7c478bd9Sstevel@tonic-gate if (sm_strlcpy(fbuf, fn, sizeof fbuf) >= sizeof fbuf) 70*7c478bd9Sstevel@tonic-gate { 71*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 72*7c478bd9Sstevel@tonic-gate sm_dprintf("\tpathname too long\n"); 73*7c478bd9Sstevel@tonic-gate return ENAMETOOLONG; 74*7c478bd9Sstevel@tonic-gate } 75*7c478bd9Sstevel@tonic-gate fn = fbuf; 76*7c478bd9Sstevel@tonic-gate if (st == NULL) 77*7c478bd9Sstevel@tonic-gate st = &fstbuf; 78*7c478bd9Sstevel@tonic-gate 79*7c478bd9Sstevel@tonic-gate /* ignore SFF_SAFEDIRPATH if we are debugging */ 80*7c478bd9Sstevel@tonic-gate if (RealUid != 0 && RunAsUid == RealUid) 81*7c478bd9Sstevel@tonic-gate flags &= ~SFF_SAFEDIRPATH; 82*7c478bd9Sstevel@tonic-gate 83*7c478bd9Sstevel@tonic-gate /* first check to see if the file exists at all */ 84*7c478bd9Sstevel@tonic-gate # if HASLSTAT 85*7c478bd9Sstevel@tonic-gate if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st) 86*7c478bd9Sstevel@tonic-gate : stat(fn, st)) < 0) 87*7c478bd9Sstevel@tonic-gate # else /* HASLSTAT */ 88*7c478bd9Sstevel@tonic-gate if (stat(fn, st) < 0) 89*7c478bd9Sstevel@tonic-gate # endif /* HASLSTAT */ 90*7c478bd9Sstevel@tonic-gate { 91*7c478bd9Sstevel@tonic-gate file_errno = errno; 92*7c478bd9Sstevel@tonic-gate } 93*7c478bd9Sstevel@tonic-gate else if (bitset(SFF_SETUIDOK, flags) && 94*7c478bd9Sstevel@tonic-gate !bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode) && 95*7c478bd9Sstevel@tonic-gate S_ISREG(st->st_mode)) 96*7c478bd9Sstevel@tonic-gate { 97*7c478bd9Sstevel@tonic-gate /* 98*7c478bd9Sstevel@tonic-gate ** If final file is set-user-ID, run as the owner of that 99*7c478bd9Sstevel@tonic-gate ** file. Gotta be careful not to reveal anything too 100*7c478bd9Sstevel@tonic-gate ** soon here! 101*7c478bd9Sstevel@tonic-gate */ 102*7c478bd9Sstevel@tonic-gate 103*7c478bd9Sstevel@tonic-gate # ifdef SUID_ROOT_FILES_OK 104*7c478bd9Sstevel@tonic-gate if (bitset(S_ISUID, st->st_mode)) 105*7c478bd9Sstevel@tonic-gate # else /* SUID_ROOT_FILES_OK */ 106*7c478bd9Sstevel@tonic-gate if (bitset(S_ISUID, st->st_mode) && st->st_uid != 0 && 107*7c478bd9Sstevel@tonic-gate st->st_uid != TrustedUid) 108*7c478bd9Sstevel@tonic-gate # endif /* SUID_ROOT_FILES_OK */ 109*7c478bd9Sstevel@tonic-gate { 110*7c478bd9Sstevel@tonic-gate uid = st->st_uid; 111*7c478bd9Sstevel@tonic-gate user = NULL; 112*7c478bd9Sstevel@tonic-gate } 113*7c478bd9Sstevel@tonic-gate # ifdef SUID_ROOT_FILES_OK 114*7c478bd9Sstevel@tonic-gate if (bitset(S_ISGID, st->st_mode)) 115*7c478bd9Sstevel@tonic-gate # else /* SUID_ROOT_FILES_OK */ 116*7c478bd9Sstevel@tonic-gate if (bitset(S_ISGID, st->st_mode) && st->st_gid != 0) 117*7c478bd9Sstevel@tonic-gate # endif /* SUID_ROOT_FILES_OK */ 118*7c478bd9Sstevel@tonic-gate gid = st->st_gid; 119*7c478bd9Sstevel@tonic-gate } 120*7c478bd9Sstevel@tonic-gate 121*7c478bd9Sstevel@tonic-gate checkpath = !bitset(SFF_NOPATHCHECK, flags) || 122*7c478bd9Sstevel@tonic-gate (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags)); 123*7c478bd9Sstevel@tonic-gate if (bitset(SFF_NOWLINK, flags) && !bitset(SFF_SAFEDIRPATH, flags)) 124*7c478bd9Sstevel@tonic-gate { 125*7c478bd9Sstevel@tonic-gate int ret; 126*7c478bd9Sstevel@tonic-gate 127*7c478bd9Sstevel@tonic-gate /* check the directory */ 128*7c478bd9Sstevel@tonic-gate p = strrchr(fn, '/'); 129*7c478bd9Sstevel@tonic-gate if (p == NULL) 130*7c478bd9Sstevel@tonic-gate { 131*7c478bd9Sstevel@tonic-gate ret = safedirpath(".", uid, gid, user, 132*7c478bd9Sstevel@tonic-gate flags|SFF_SAFEDIRPATH, 0, 0); 133*7c478bd9Sstevel@tonic-gate } 134*7c478bd9Sstevel@tonic-gate else 135*7c478bd9Sstevel@tonic-gate { 136*7c478bd9Sstevel@tonic-gate *p = '\0'; 137*7c478bd9Sstevel@tonic-gate ret = safedirpath(fn, uid, gid, user, 138*7c478bd9Sstevel@tonic-gate flags|SFF_SAFEDIRPATH, 0, 0); 139*7c478bd9Sstevel@tonic-gate *p = '/'; 140*7c478bd9Sstevel@tonic-gate } 141*7c478bd9Sstevel@tonic-gate if (ret == 0) 142*7c478bd9Sstevel@tonic-gate { 143*7c478bd9Sstevel@tonic-gate /* directory is safe */ 144*7c478bd9Sstevel@tonic-gate checkpath = false; 145*7c478bd9Sstevel@tonic-gate } 146*7c478bd9Sstevel@tonic-gate else 147*7c478bd9Sstevel@tonic-gate { 148*7c478bd9Sstevel@tonic-gate # if HASLSTAT 149*7c478bd9Sstevel@tonic-gate /* Need lstat() information if called stat() before */ 150*7c478bd9Sstevel@tonic-gate if (!bitset(SFF_NOSLINK, flags) && lstat(fn, st) < 0) 151*7c478bd9Sstevel@tonic-gate { 152*7c478bd9Sstevel@tonic-gate ret = errno; 153*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 154*7c478bd9Sstevel@tonic-gate sm_dprintf("\t%s\n", sm_errstring(ret)); 155*7c478bd9Sstevel@tonic-gate return ret; 156*7c478bd9Sstevel@tonic-gate } 157*7c478bd9Sstevel@tonic-gate # endif /* HASLSTAT */ 158*7c478bd9Sstevel@tonic-gate /* directory is writable: disallow links */ 159*7c478bd9Sstevel@tonic-gate flags |= SFF_NOLINK; 160*7c478bd9Sstevel@tonic-gate } 161*7c478bd9Sstevel@tonic-gate } 162*7c478bd9Sstevel@tonic-gate 163*7c478bd9Sstevel@tonic-gate if (checkpath) 164*7c478bd9Sstevel@tonic-gate { 165*7c478bd9Sstevel@tonic-gate int ret; 166*7c478bd9Sstevel@tonic-gate 167*7c478bd9Sstevel@tonic-gate p = strrchr(fn, '/'); 168*7c478bd9Sstevel@tonic-gate if (p == NULL) 169*7c478bd9Sstevel@tonic-gate { 170*7c478bd9Sstevel@tonic-gate ret = safedirpath(".", uid, gid, user, flags, 0, 0); 171*7c478bd9Sstevel@tonic-gate } 172*7c478bd9Sstevel@tonic-gate else 173*7c478bd9Sstevel@tonic-gate { 174*7c478bd9Sstevel@tonic-gate *p = '\0'; 175*7c478bd9Sstevel@tonic-gate ret = safedirpath(fn, uid, gid, user, flags, 0, 0); 176*7c478bd9Sstevel@tonic-gate *p = '/'; 177*7c478bd9Sstevel@tonic-gate } 178*7c478bd9Sstevel@tonic-gate if (ret != 0) 179*7c478bd9Sstevel@tonic-gate return ret; 180*7c478bd9Sstevel@tonic-gate } 181*7c478bd9Sstevel@tonic-gate 182*7c478bd9Sstevel@tonic-gate /* 183*7c478bd9Sstevel@tonic-gate ** If the target file doesn't exist, check the directory to 184*7c478bd9Sstevel@tonic-gate ** ensure that it is writable by this user. 185*7c478bd9Sstevel@tonic-gate */ 186*7c478bd9Sstevel@tonic-gate 187*7c478bd9Sstevel@tonic-gate if (file_errno != 0) 188*7c478bd9Sstevel@tonic-gate { 189*7c478bd9Sstevel@tonic-gate int ret = file_errno; 190*7c478bd9Sstevel@tonic-gate char *dir = fn; 191*7c478bd9Sstevel@tonic-gate 192*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 193*7c478bd9Sstevel@tonic-gate sm_dprintf("\t%s\n", sm_errstring(ret)); 194*7c478bd9Sstevel@tonic-gate 195*7c478bd9Sstevel@tonic-gate errno = 0; 196*7c478bd9Sstevel@tonic-gate if (!bitset(SFF_CREAT, flags) || file_errno != ENOENT) 197*7c478bd9Sstevel@tonic-gate return ret; 198*7c478bd9Sstevel@tonic-gate 199*7c478bd9Sstevel@tonic-gate /* check to see if legal to create the file */ 200*7c478bd9Sstevel@tonic-gate p = strrchr(dir, '/'); 201*7c478bd9Sstevel@tonic-gate if (p == NULL) 202*7c478bd9Sstevel@tonic-gate dir = "."; 203*7c478bd9Sstevel@tonic-gate else if (p == dir) 204*7c478bd9Sstevel@tonic-gate dir = "/"; 205*7c478bd9Sstevel@tonic-gate else 206*7c478bd9Sstevel@tonic-gate *p = '\0'; 207*7c478bd9Sstevel@tonic-gate if (stat(dir, &stbuf) >= 0) 208*7c478bd9Sstevel@tonic-gate { 209*7c478bd9Sstevel@tonic-gate int md = S_IWRITE|S_IEXEC; 210*7c478bd9Sstevel@tonic-gate 211*7c478bd9Sstevel@tonic-gate ret = 0; 212*7c478bd9Sstevel@tonic-gate if (stbuf.st_uid == uid) 213*7c478bd9Sstevel@tonic-gate /* EMPTY */ 214*7c478bd9Sstevel@tonic-gate ; 215*7c478bd9Sstevel@tonic-gate else if (uid == 0 && stbuf.st_uid == TrustedUid) 216*7c478bd9Sstevel@tonic-gate /* EMPTY */ 217*7c478bd9Sstevel@tonic-gate ; 218*7c478bd9Sstevel@tonic-gate else 219*7c478bd9Sstevel@tonic-gate { 220*7c478bd9Sstevel@tonic-gate md >>= 3; 221*7c478bd9Sstevel@tonic-gate if (stbuf.st_gid == gid) 222*7c478bd9Sstevel@tonic-gate /* EMPTY */ 223*7c478bd9Sstevel@tonic-gate ; 224*7c478bd9Sstevel@tonic-gate # ifndef NO_GROUP_SET 225*7c478bd9Sstevel@tonic-gate else if (user != NULL && !DontInitGroups && 226*7c478bd9Sstevel@tonic-gate ((gr != NULL && 227*7c478bd9Sstevel@tonic-gate gr->gr_gid == stbuf.st_gid) || 228*7c478bd9Sstevel@tonic-gate (gr = getgrgid(stbuf.st_gid)) != NULL)) 229*7c478bd9Sstevel@tonic-gate { 230*7c478bd9Sstevel@tonic-gate register char **gp; 231*7c478bd9Sstevel@tonic-gate 232*7c478bd9Sstevel@tonic-gate for (gp = gr->gr_mem; *gp != NULL; gp++) 233*7c478bd9Sstevel@tonic-gate if (strcmp(*gp, user) == 0) 234*7c478bd9Sstevel@tonic-gate break; 235*7c478bd9Sstevel@tonic-gate if (*gp == NULL) 236*7c478bd9Sstevel@tonic-gate md >>= 3; 237*7c478bd9Sstevel@tonic-gate } 238*7c478bd9Sstevel@tonic-gate # endif /* ! NO_GROUP_SET */ 239*7c478bd9Sstevel@tonic-gate else 240*7c478bd9Sstevel@tonic-gate md >>= 3; 241*7c478bd9Sstevel@tonic-gate } 242*7c478bd9Sstevel@tonic-gate if ((stbuf.st_mode & md) != md) 243*7c478bd9Sstevel@tonic-gate ret = errno = EACCES; 244*7c478bd9Sstevel@tonic-gate } 245*7c478bd9Sstevel@tonic-gate else 246*7c478bd9Sstevel@tonic-gate ret = errno; 247*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 248*7c478bd9Sstevel@tonic-gate sm_dprintf("\t[final dir %s uid %d mode %lo] %s\n", 249*7c478bd9Sstevel@tonic-gate dir, (int) stbuf.st_uid, 250*7c478bd9Sstevel@tonic-gate (unsigned long) stbuf.st_mode, 251*7c478bd9Sstevel@tonic-gate sm_errstring(ret)); 252*7c478bd9Sstevel@tonic-gate if (p != NULL) 253*7c478bd9Sstevel@tonic-gate *p = '/'; 254*7c478bd9Sstevel@tonic-gate st->st_mode = ST_MODE_NOFILE; 255*7c478bd9Sstevel@tonic-gate return ret; 256*7c478bd9Sstevel@tonic-gate } 257*7c478bd9Sstevel@tonic-gate 258*7c478bd9Sstevel@tonic-gate # ifdef S_ISLNK 259*7c478bd9Sstevel@tonic-gate if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode)) 260*7c478bd9Sstevel@tonic-gate { 261*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 262*7c478bd9Sstevel@tonic-gate sm_dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n", 263*7c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 264*7c478bd9Sstevel@tonic-gate return E_SM_NOSLINK; 265*7c478bd9Sstevel@tonic-gate } 266*7c478bd9Sstevel@tonic-gate # endif /* S_ISLNK */ 267*7c478bd9Sstevel@tonic-gate if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode)) 268*7c478bd9Sstevel@tonic-gate { 269*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 270*7c478bd9Sstevel@tonic-gate sm_dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n", 271*7c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 272*7c478bd9Sstevel@tonic-gate return E_SM_REGONLY; 273*7c478bd9Sstevel@tonic-gate } 274*7c478bd9Sstevel@tonic-gate if (bitset(SFF_NOGWFILES, flags) && 275*7c478bd9Sstevel@tonic-gate bitset(S_IWGRP, st->st_mode)) 276*7c478bd9Sstevel@tonic-gate { 277*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 278*7c478bd9Sstevel@tonic-gate sm_dprintf("\t[write bits %lo]\tE_SM_GWFILE\n", 279*7c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 280*7c478bd9Sstevel@tonic-gate return E_SM_GWFILE; 281*7c478bd9Sstevel@tonic-gate } 282*7c478bd9Sstevel@tonic-gate if (bitset(SFF_NOWWFILES, flags) && 283*7c478bd9Sstevel@tonic-gate bitset(S_IWOTH, st->st_mode)) 284*7c478bd9Sstevel@tonic-gate { 285*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 286*7c478bd9Sstevel@tonic-gate sm_dprintf("\t[write bits %lo]\tE_SM_WWFILE\n", 287*7c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 288*7c478bd9Sstevel@tonic-gate return E_SM_WWFILE; 289*7c478bd9Sstevel@tonic-gate } 290*7c478bd9Sstevel@tonic-gate if (bitset(SFF_NOGRFILES, flags) && bitset(S_IRGRP, st->st_mode)) 291*7c478bd9Sstevel@tonic-gate { 292*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 293*7c478bd9Sstevel@tonic-gate sm_dprintf("\t[read bits %lo]\tE_SM_GRFILE\n", 294*7c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 295*7c478bd9Sstevel@tonic-gate return E_SM_GRFILE; 296*7c478bd9Sstevel@tonic-gate } 297*7c478bd9Sstevel@tonic-gate if (bitset(SFF_NOWRFILES, flags) && bitset(S_IROTH, st->st_mode)) 298*7c478bd9Sstevel@tonic-gate { 299*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 300*7c478bd9Sstevel@tonic-gate sm_dprintf("\t[read bits %lo]\tE_SM_WRFILE\n", 301*7c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 302*7c478bd9Sstevel@tonic-gate return E_SM_WRFILE; 303*7c478bd9Sstevel@tonic-gate } 304*7c478bd9Sstevel@tonic-gate if (!bitset(SFF_EXECOK, flags) && 305*7c478bd9Sstevel@tonic-gate bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) && 306*7c478bd9Sstevel@tonic-gate bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode)) 307*7c478bd9Sstevel@tonic-gate { 308*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 309*7c478bd9Sstevel@tonic-gate sm_dprintf("\t[exec bits %lo]\tE_SM_ISEXEC\n", 310*7c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 311*7c478bd9Sstevel@tonic-gate return E_SM_ISEXEC; 312*7c478bd9Sstevel@tonic-gate } 313*7c478bd9Sstevel@tonic-gate if (bitset(SFF_NOHLINK, flags) && st->st_nlink != 1) 314*7c478bd9Sstevel@tonic-gate { 315*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 316*7c478bd9Sstevel@tonic-gate sm_dprintf("\t[link count %d]\tE_SM_NOHLINK\n", 317*7c478bd9Sstevel@tonic-gate (int) st->st_nlink); 318*7c478bd9Sstevel@tonic-gate return E_SM_NOHLINK; 319*7c478bd9Sstevel@tonic-gate } 320*7c478bd9Sstevel@tonic-gate 321*7c478bd9Sstevel@tonic-gate if (uid == 0 && bitset(SFF_OPENASROOT, flags)) 322*7c478bd9Sstevel@tonic-gate /* EMPTY */ 323*7c478bd9Sstevel@tonic-gate ; 324*7c478bd9Sstevel@tonic-gate else if (uid == 0 && !bitset(SFF_ROOTOK, flags)) 325*7c478bd9Sstevel@tonic-gate mode >>= 6; 326*7c478bd9Sstevel@tonic-gate else if (st->st_uid == uid) 327*7c478bd9Sstevel@tonic-gate /* EMPTY */ 328*7c478bd9Sstevel@tonic-gate ; 329*7c478bd9Sstevel@tonic-gate else if (uid == 0 && st->st_uid == TrustedUid) 330*7c478bd9Sstevel@tonic-gate /* EMPTY */ 331*7c478bd9Sstevel@tonic-gate ; 332*7c478bd9Sstevel@tonic-gate else 333*7c478bd9Sstevel@tonic-gate { 334*7c478bd9Sstevel@tonic-gate mode >>= 3; 335*7c478bd9Sstevel@tonic-gate if (st->st_gid == gid) 336*7c478bd9Sstevel@tonic-gate /* EMPTY */ 337*7c478bd9Sstevel@tonic-gate ; 338*7c478bd9Sstevel@tonic-gate # ifndef NO_GROUP_SET 339*7c478bd9Sstevel@tonic-gate else if (user != NULL && !DontInitGroups && 340*7c478bd9Sstevel@tonic-gate ((gr != NULL && gr->gr_gid == st->st_gid) || 341*7c478bd9Sstevel@tonic-gate (gr = getgrgid(st->st_gid)) != NULL)) 342*7c478bd9Sstevel@tonic-gate { 343*7c478bd9Sstevel@tonic-gate register char **gp; 344*7c478bd9Sstevel@tonic-gate 345*7c478bd9Sstevel@tonic-gate for (gp = gr->gr_mem; *gp != NULL; gp++) 346*7c478bd9Sstevel@tonic-gate if (strcmp(*gp, user) == 0) 347*7c478bd9Sstevel@tonic-gate break; 348*7c478bd9Sstevel@tonic-gate if (*gp == NULL) 349*7c478bd9Sstevel@tonic-gate mode >>= 3; 350*7c478bd9Sstevel@tonic-gate } 351*7c478bd9Sstevel@tonic-gate # endif /* ! NO_GROUP_SET */ 352*7c478bd9Sstevel@tonic-gate else 353*7c478bd9Sstevel@tonic-gate mode >>= 3; 354*7c478bd9Sstevel@tonic-gate } 355*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 356*7c478bd9Sstevel@tonic-gate sm_dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ", 357*7c478bd9Sstevel@tonic-gate (int) st->st_uid, (int) st->st_nlink, 358*7c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode, (unsigned long) mode); 359*7c478bd9Sstevel@tonic-gate if ((st->st_uid == uid || st->st_uid == 0 || 360*7c478bd9Sstevel@tonic-gate st->st_uid == TrustedUid || 361*7c478bd9Sstevel@tonic-gate !bitset(SFF_MUSTOWN, flags)) && 362*7c478bd9Sstevel@tonic-gate (st->st_mode & mode) == mode) 363*7c478bd9Sstevel@tonic-gate { 364*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 365*7c478bd9Sstevel@tonic-gate sm_dprintf("\tOK\n"); 366*7c478bd9Sstevel@tonic-gate return 0; 367*7c478bd9Sstevel@tonic-gate } 368*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 369*7c478bd9Sstevel@tonic-gate sm_dprintf("\tEACCES\n"); 370*7c478bd9Sstevel@tonic-gate return EACCES; 371*7c478bd9Sstevel@tonic-gate } 372*7c478bd9Sstevel@tonic-gate /* 373*7c478bd9Sstevel@tonic-gate ** SAFEDIRPATH -- check to make sure a path to a directory is safe 374*7c478bd9Sstevel@tonic-gate ** 375*7c478bd9Sstevel@tonic-gate ** Safe means not writable and owned by the right folks. 376*7c478bd9Sstevel@tonic-gate ** 377*7c478bd9Sstevel@tonic-gate ** Parameters: 378*7c478bd9Sstevel@tonic-gate ** fn -- filename to check. 379*7c478bd9Sstevel@tonic-gate ** uid -- user id to compare against. 380*7c478bd9Sstevel@tonic-gate ** gid -- group id to compare against. 381*7c478bd9Sstevel@tonic-gate ** user -- user name to compare against (used for group 382*7c478bd9Sstevel@tonic-gate ** sets). 383*7c478bd9Sstevel@tonic-gate ** flags -- modifiers: 384*7c478bd9Sstevel@tonic-gate ** SFF_ROOTOK -- ok to use root permissions to open. 385*7c478bd9Sstevel@tonic-gate ** SFF_SAFEDIRPATH -- writable directories are considered 386*7c478bd9Sstevel@tonic-gate ** to be fatal errors. 387*7c478bd9Sstevel@tonic-gate ** level -- symlink recursive level. 388*7c478bd9Sstevel@tonic-gate ** offset -- offset into fn to start checking from. 389*7c478bd9Sstevel@tonic-gate ** 390*7c478bd9Sstevel@tonic-gate ** Returns: 391*7c478bd9Sstevel@tonic-gate ** 0 -- if the directory path is "safe". 392*7c478bd9Sstevel@tonic-gate ** else -- an error number associated with the path. 393*7c478bd9Sstevel@tonic-gate */ 394*7c478bd9Sstevel@tonic-gate 395*7c478bd9Sstevel@tonic-gate int 396*7c478bd9Sstevel@tonic-gate safedirpath(fn, uid, gid, user, flags, level, offset) 397*7c478bd9Sstevel@tonic-gate char *fn; 398*7c478bd9Sstevel@tonic-gate UID_T uid; 399*7c478bd9Sstevel@tonic-gate GID_T gid; 400*7c478bd9Sstevel@tonic-gate char *user; 401*7c478bd9Sstevel@tonic-gate long flags; 402*7c478bd9Sstevel@tonic-gate int level; 403*7c478bd9Sstevel@tonic-gate int offset; 404*7c478bd9Sstevel@tonic-gate { 405*7c478bd9Sstevel@tonic-gate int ret = 0; 406*7c478bd9Sstevel@tonic-gate int mode = S_IWOTH; 407*7c478bd9Sstevel@tonic-gate char save = '\0'; 408*7c478bd9Sstevel@tonic-gate char *saveptr = NULL; 409*7c478bd9Sstevel@tonic-gate char *p, *enddir; 410*7c478bd9Sstevel@tonic-gate register struct group *gr = NULL; 411*7c478bd9Sstevel@tonic-gate char s[MAXLINKPATHLEN]; 412*7c478bd9Sstevel@tonic-gate struct stat stbuf; 413*7c478bd9Sstevel@tonic-gate 414*7c478bd9Sstevel@tonic-gate /* make sure we aren't in a symlink loop */ 415*7c478bd9Sstevel@tonic-gate if (level > MAXSYMLINKS) 416*7c478bd9Sstevel@tonic-gate return ELOOP; 417*7c478bd9Sstevel@tonic-gate 418*7c478bd9Sstevel@tonic-gate if (level < 0 || offset < 0 || offset > strlen(fn)) 419*7c478bd9Sstevel@tonic-gate return EINVAL; 420*7c478bd9Sstevel@tonic-gate 421*7c478bd9Sstevel@tonic-gate /* special case root directory */ 422*7c478bd9Sstevel@tonic-gate if (*fn == '\0') 423*7c478bd9Sstevel@tonic-gate fn = "/"; 424*7c478bd9Sstevel@tonic-gate 425*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 426*7c478bd9Sstevel@tonic-gate sm_dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n", 427*7c478bd9Sstevel@tonic-gate fn, (long) uid, (long) gid, flags, level, offset); 428*7c478bd9Sstevel@tonic-gate 429*7c478bd9Sstevel@tonic-gate if (!bitnset(DBS_GROUPWRITABLEDIRPATHSAFE, DontBlameSendmail)) 430*7c478bd9Sstevel@tonic-gate mode |= S_IWGRP; 431*7c478bd9Sstevel@tonic-gate 432*7c478bd9Sstevel@tonic-gate /* Make a modifiable copy of the filename */ 433*7c478bd9Sstevel@tonic-gate if (sm_strlcpy(s, fn, sizeof s) >= sizeof s) 434*7c478bd9Sstevel@tonic-gate return EINVAL; 435*7c478bd9Sstevel@tonic-gate 436*7c478bd9Sstevel@tonic-gate p = s + offset; 437*7c478bd9Sstevel@tonic-gate while (p != NULL) 438*7c478bd9Sstevel@tonic-gate { 439*7c478bd9Sstevel@tonic-gate /* put back character */ 440*7c478bd9Sstevel@tonic-gate if (saveptr != NULL) 441*7c478bd9Sstevel@tonic-gate { 442*7c478bd9Sstevel@tonic-gate *saveptr = save; 443*7c478bd9Sstevel@tonic-gate saveptr = NULL; 444*7c478bd9Sstevel@tonic-gate p++; 445*7c478bd9Sstevel@tonic-gate } 446*7c478bd9Sstevel@tonic-gate 447*7c478bd9Sstevel@tonic-gate if (*p == '\0') 448*7c478bd9Sstevel@tonic-gate break; 449*7c478bd9Sstevel@tonic-gate 450*7c478bd9Sstevel@tonic-gate p = strchr(p, '/'); 451*7c478bd9Sstevel@tonic-gate 452*7c478bd9Sstevel@tonic-gate /* Special case for root directory */ 453*7c478bd9Sstevel@tonic-gate if (p == s) 454*7c478bd9Sstevel@tonic-gate { 455*7c478bd9Sstevel@tonic-gate save = *(p + 1); 456*7c478bd9Sstevel@tonic-gate saveptr = p + 1; 457*7c478bd9Sstevel@tonic-gate *(p + 1) = '\0'; 458*7c478bd9Sstevel@tonic-gate } 459*7c478bd9Sstevel@tonic-gate else if (p != NULL) 460*7c478bd9Sstevel@tonic-gate { 461*7c478bd9Sstevel@tonic-gate save = *p; 462*7c478bd9Sstevel@tonic-gate saveptr = p; 463*7c478bd9Sstevel@tonic-gate *p = '\0'; 464*7c478bd9Sstevel@tonic-gate } 465*7c478bd9Sstevel@tonic-gate 466*7c478bd9Sstevel@tonic-gate /* Heuristic: . and .. have already been checked */ 467*7c478bd9Sstevel@tonic-gate enddir = strrchr(s, '/'); 468*7c478bd9Sstevel@tonic-gate if (enddir != NULL && 469*7c478bd9Sstevel@tonic-gate (strcmp(enddir, "/..") == 0 || 470*7c478bd9Sstevel@tonic-gate strcmp(enddir, "/.") == 0)) 471*7c478bd9Sstevel@tonic-gate continue; 472*7c478bd9Sstevel@tonic-gate 473*7c478bd9Sstevel@tonic-gate if (tTd(44, 20)) 474*7c478bd9Sstevel@tonic-gate sm_dprintf("\t[dir %s]\n", s); 475*7c478bd9Sstevel@tonic-gate 476*7c478bd9Sstevel@tonic-gate # if HASLSTAT 477*7c478bd9Sstevel@tonic-gate ret = lstat(s, &stbuf); 478*7c478bd9Sstevel@tonic-gate # else /* HASLSTAT */ 479*7c478bd9Sstevel@tonic-gate ret = stat(s, &stbuf); 480*7c478bd9Sstevel@tonic-gate # endif /* HASLSTAT */ 481*7c478bd9Sstevel@tonic-gate if (ret < 0) 482*7c478bd9Sstevel@tonic-gate { 483*7c478bd9Sstevel@tonic-gate ret = errno; 484*7c478bd9Sstevel@tonic-gate break; 485*7c478bd9Sstevel@tonic-gate } 486*7c478bd9Sstevel@tonic-gate 487*7c478bd9Sstevel@tonic-gate # ifdef S_ISLNK 488*7c478bd9Sstevel@tonic-gate /* Follow symlinks */ 489*7c478bd9Sstevel@tonic-gate if (S_ISLNK(stbuf.st_mode)) 490*7c478bd9Sstevel@tonic-gate { 491*7c478bd9Sstevel@tonic-gate int linklen; 492*7c478bd9Sstevel@tonic-gate char *target; 493*7c478bd9Sstevel@tonic-gate char buf[MAXPATHLEN]; 494*7c478bd9Sstevel@tonic-gate char fullbuf[MAXLINKPATHLEN]; 495*7c478bd9Sstevel@tonic-gate 496*7c478bd9Sstevel@tonic-gate memset(buf, '\0', sizeof buf); 497*7c478bd9Sstevel@tonic-gate linklen = readlink(s, buf, sizeof buf); 498*7c478bd9Sstevel@tonic-gate if (linklen < 0) 499*7c478bd9Sstevel@tonic-gate { 500*7c478bd9Sstevel@tonic-gate ret = errno; 501*7c478bd9Sstevel@tonic-gate break; 502*7c478bd9Sstevel@tonic-gate } 503*7c478bd9Sstevel@tonic-gate if (linklen >= sizeof buf) 504*7c478bd9Sstevel@tonic-gate { 505*7c478bd9Sstevel@tonic-gate /* file name too long for buffer */ 506*7c478bd9Sstevel@tonic-gate ret = errno = EINVAL; 507*7c478bd9Sstevel@tonic-gate break; 508*7c478bd9Sstevel@tonic-gate } 509*7c478bd9Sstevel@tonic-gate 510*7c478bd9Sstevel@tonic-gate offset = 0; 511*7c478bd9Sstevel@tonic-gate if (*buf == '/') 512*7c478bd9Sstevel@tonic-gate { 513*7c478bd9Sstevel@tonic-gate target = buf; 514*7c478bd9Sstevel@tonic-gate 515*7c478bd9Sstevel@tonic-gate /* If path is the same, avoid rechecks */ 516*7c478bd9Sstevel@tonic-gate while (s[offset] == buf[offset] && 517*7c478bd9Sstevel@tonic-gate s[offset] != '\0') 518*7c478bd9Sstevel@tonic-gate offset++; 519*7c478bd9Sstevel@tonic-gate 520*7c478bd9Sstevel@tonic-gate if (s[offset] == '\0' && buf[offset] == '\0') 521*7c478bd9Sstevel@tonic-gate { 522*7c478bd9Sstevel@tonic-gate /* strings match, symlink loop */ 523*7c478bd9Sstevel@tonic-gate return ELOOP; 524*7c478bd9Sstevel@tonic-gate } 525*7c478bd9Sstevel@tonic-gate 526*7c478bd9Sstevel@tonic-gate /* back off from the mismatch */ 527*7c478bd9Sstevel@tonic-gate if (offset > 0) 528*7c478bd9Sstevel@tonic-gate offset--; 529*7c478bd9Sstevel@tonic-gate 530*7c478bd9Sstevel@tonic-gate /* Make sure we are at a directory break */ 531*7c478bd9Sstevel@tonic-gate if (offset > 0 && 532*7c478bd9Sstevel@tonic-gate s[offset] != '/' && 533*7c478bd9Sstevel@tonic-gate s[offset] != '\0') 534*7c478bd9Sstevel@tonic-gate { 535*7c478bd9Sstevel@tonic-gate while (buf[offset] != '/' && 536*7c478bd9Sstevel@tonic-gate offset > 0) 537*7c478bd9Sstevel@tonic-gate offset--; 538*7c478bd9Sstevel@tonic-gate } 539*7c478bd9Sstevel@tonic-gate if (offset > 0 && 540*7c478bd9Sstevel@tonic-gate s[offset] == '/' && 541*7c478bd9Sstevel@tonic-gate buf[offset] == '/') 542*7c478bd9Sstevel@tonic-gate { 543*7c478bd9Sstevel@tonic-gate /* Include the trailing slash */ 544*7c478bd9Sstevel@tonic-gate offset++; 545*7c478bd9Sstevel@tonic-gate } 546*7c478bd9Sstevel@tonic-gate } 547*7c478bd9Sstevel@tonic-gate else 548*7c478bd9Sstevel@tonic-gate { 549*7c478bd9Sstevel@tonic-gate char *sptr; 550*7c478bd9Sstevel@tonic-gate 551*7c478bd9Sstevel@tonic-gate sptr = strrchr(s, '/'); 552*7c478bd9Sstevel@tonic-gate if (sptr != NULL) 553*7c478bd9Sstevel@tonic-gate { 554*7c478bd9Sstevel@tonic-gate *sptr = '\0'; 555*7c478bd9Sstevel@tonic-gate offset = sptr + 1 - s; 556*7c478bd9Sstevel@tonic-gate if (sm_strlcpyn(fullbuf, 557*7c478bd9Sstevel@tonic-gate sizeof fullbuf, 2, 558*7c478bd9Sstevel@tonic-gate s, "/") >= 559*7c478bd9Sstevel@tonic-gate sizeof fullbuf || 560*7c478bd9Sstevel@tonic-gate sm_strlcat(fullbuf, buf, 561*7c478bd9Sstevel@tonic-gate sizeof fullbuf) >= 562*7c478bd9Sstevel@tonic-gate sizeof fullbuf) 563*7c478bd9Sstevel@tonic-gate { 564*7c478bd9Sstevel@tonic-gate ret = EINVAL; 565*7c478bd9Sstevel@tonic-gate break; 566*7c478bd9Sstevel@tonic-gate } 567*7c478bd9Sstevel@tonic-gate *sptr = '/'; 568*7c478bd9Sstevel@tonic-gate } 569*7c478bd9Sstevel@tonic-gate else 570*7c478bd9Sstevel@tonic-gate { 571*7c478bd9Sstevel@tonic-gate if (sm_strlcpy(fullbuf, buf, 572*7c478bd9Sstevel@tonic-gate sizeof fullbuf) >= 573*7c478bd9Sstevel@tonic-gate sizeof fullbuf) 574*7c478bd9Sstevel@tonic-gate { 575*7c478bd9Sstevel@tonic-gate ret = EINVAL; 576*7c478bd9Sstevel@tonic-gate break; 577*7c478bd9Sstevel@tonic-gate } 578*7c478bd9Sstevel@tonic-gate } 579*7c478bd9Sstevel@tonic-gate target = fullbuf; 580*7c478bd9Sstevel@tonic-gate } 581*7c478bd9Sstevel@tonic-gate ret = safedirpath(target, uid, gid, user, flags, 582*7c478bd9Sstevel@tonic-gate level + 1, offset); 583*7c478bd9Sstevel@tonic-gate if (ret != 0) 584*7c478bd9Sstevel@tonic-gate break; 585*7c478bd9Sstevel@tonic-gate 586*7c478bd9Sstevel@tonic-gate /* Don't check permissions on the link file itself */ 587*7c478bd9Sstevel@tonic-gate continue; 588*7c478bd9Sstevel@tonic-gate } 589*7c478bd9Sstevel@tonic-gate #endif /* S_ISLNK */ 590*7c478bd9Sstevel@tonic-gate 591*7c478bd9Sstevel@tonic-gate if ((uid == 0 || bitset(SFF_SAFEDIRPATH, flags)) && 592*7c478bd9Sstevel@tonic-gate #ifdef S_ISVTX 593*7c478bd9Sstevel@tonic-gate !(bitnset(DBS_TRUSTSTICKYBIT, DontBlameSendmail) && 594*7c478bd9Sstevel@tonic-gate bitset(S_ISVTX, stbuf.st_mode)) && 595*7c478bd9Sstevel@tonic-gate #endif /* S_ISVTX */ 596*7c478bd9Sstevel@tonic-gate bitset(mode, stbuf.st_mode)) 597*7c478bd9Sstevel@tonic-gate { 598*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 599*7c478bd9Sstevel@tonic-gate sm_dprintf("\t[dir %s] mode %lo ", 600*7c478bd9Sstevel@tonic-gate s, (unsigned long) stbuf.st_mode); 601*7c478bd9Sstevel@tonic-gate if (bitset(SFF_SAFEDIRPATH, flags)) 602*7c478bd9Sstevel@tonic-gate { 603*7c478bd9Sstevel@tonic-gate if (bitset(S_IWOTH, stbuf.st_mode)) 604*7c478bd9Sstevel@tonic-gate ret = E_SM_WWDIR; 605*7c478bd9Sstevel@tonic-gate else 606*7c478bd9Sstevel@tonic-gate ret = E_SM_GWDIR; 607*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 608*7c478bd9Sstevel@tonic-gate sm_dprintf("FATAL\n"); 609*7c478bd9Sstevel@tonic-gate break; 610*7c478bd9Sstevel@tonic-gate } 611*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 612*7c478bd9Sstevel@tonic-gate sm_dprintf("WARNING\n"); 613*7c478bd9Sstevel@tonic-gate if (Verbose > 1) 614*7c478bd9Sstevel@tonic-gate message("051 WARNING: %s writable directory %s", 615*7c478bd9Sstevel@tonic-gate bitset(S_IWOTH, stbuf.st_mode) 616*7c478bd9Sstevel@tonic-gate ? "World" 617*7c478bd9Sstevel@tonic-gate : "Group", 618*7c478bd9Sstevel@tonic-gate s); 619*7c478bd9Sstevel@tonic-gate } 620*7c478bd9Sstevel@tonic-gate if (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags)) 621*7c478bd9Sstevel@tonic-gate { 622*7c478bd9Sstevel@tonic-gate if (bitset(S_IXOTH, stbuf.st_mode)) 623*7c478bd9Sstevel@tonic-gate continue; 624*7c478bd9Sstevel@tonic-gate ret = EACCES; 625*7c478bd9Sstevel@tonic-gate break; 626*7c478bd9Sstevel@tonic-gate } 627*7c478bd9Sstevel@tonic-gate 628*7c478bd9Sstevel@tonic-gate /* 629*7c478bd9Sstevel@tonic-gate ** Let OS determine access to file if we are not 630*7c478bd9Sstevel@tonic-gate ** running as a privileged user. This allows ACLs 631*7c478bd9Sstevel@tonic-gate ** to work. Also, if opening as root, assume we can 632*7c478bd9Sstevel@tonic-gate ** scan the directory. 633*7c478bd9Sstevel@tonic-gate */ 634*7c478bd9Sstevel@tonic-gate if (geteuid() != 0 || bitset(SFF_OPENASROOT, flags)) 635*7c478bd9Sstevel@tonic-gate continue; 636*7c478bd9Sstevel@tonic-gate 637*7c478bd9Sstevel@tonic-gate if (stbuf.st_uid == uid && 638*7c478bd9Sstevel@tonic-gate bitset(S_IXUSR, stbuf.st_mode)) 639*7c478bd9Sstevel@tonic-gate continue; 640*7c478bd9Sstevel@tonic-gate if (stbuf.st_gid == gid && 641*7c478bd9Sstevel@tonic-gate bitset(S_IXGRP, stbuf.st_mode)) 642*7c478bd9Sstevel@tonic-gate continue; 643*7c478bd9Sstevel@tonic-gate # ifndef NO_GROUP_SET 644*7c478bd9Sstevel@tonic-gate if (user != NULL && !DontInitGroups && 645*7c478bd9Sstevel@tonic-gate ((gr != NULL && gr->gr_gid == stbuf.st_gid) || 646*7c478bd9Sstevel@tonic-gate (gr = getgrgid(stbuf.st_gid)) != NULL)) 647*7c478bd9Sstevel@tonic-gate { 648*7c478bd9Sstevel@tonic-gate register char **gp; 649*7c478bd9Sstevel@tonic-gate 650*7c478bd9Sstevel@tonic-gate for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++) 651*7c478bd9Sstevel@tonic-gate if (strcmp(*gp, user) == 0) 652*7c478bd9Sstevel@tonic-gate break; 653*7c478bd9Sstevel@tonic-gate if (gp != NULL && *gp != NULL && 654*7c478bd9Sstevel@tonic-gate bitset(S_IXGRP, stbuf.st_mode)) 655*7c478bd9Sstevel@tonic-gate continue; 656*7c478bd9Sstevel@tonic-gate } 657*7c478bd9Sstevel@tonic-gate # endif /* ! NO_GROUP_SET */ 658*7c478bd9Sstevel@tonic-gate if (!bitset(S_IXOTH, stbuf.st_mode)) 659*7c478bd9Sstevel@tonic-gate { 660*7c478bd9Sstevel@tonic-gate ret = EACCES; 661*7c478bd9Sstevel@tonic-gate break; 662*7c478bd9Sstevel@tonic-gate } 663*7c478bd9Sstevel@tonic-gate } 664*7c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 665*7c478bd9Sstevel@tonic-gate sm_dprintf("\t[dir %s] %s\n", fn, 666*7c478bd9Sstevel@tonic-gate ret == 0 ? "OK" : sm_errstring(ret)); 667*7c478bd9Sstevel@tonic-gate return ret; 668*7c478bd9Sstevel@tonic-gate } 669*7c478bd9Sstevel@tonic-gate /* 670*7c478bd9Sstevel@tonic-gate ** SAFEOPEN -- do a file open with extra checking 671*7c478bd9Sstevel@tonic-gate ** 672*7c478bd9Sstevel@tonic-gate ** Parameters: 673*7c478bd9Sstevel@tonic-gate ** fn -- the file name to open. 674*7c478bd9Sstevel@tonic-gate ** omode -- the open-style mode flags. 675*7c478bd9Sstevel@tonic-gate ** cmode -- the create-style mode flags. 676*7c478bd9Sstevel@tonic-gate ** sff -- safefile flags. 677*7c478bd9Sstevel@tonic-gate ** 678*7c478bd9Sstevel@tonic-gate ** Returns: 679*7c478bd9Sstevel@tonic-gate ** Same as open. 680*7c478bd9Sstevel@tonic-gate */ 681*7c478bd9Sstevel@tonic-gate 682*7c478bd9Sstevel@tonic-gate int 683*7c478bd9Sstevel@tonic-gate safeopen(fn, omode, cmode, sff) 684*7c478bd9Sstevel@tonic-gate char *fn; 685*7c478bd9Sstevel@tonic-gate int omode; 686*7c478bd9Sstevel@tonic-gate int cmode; 687*7c478bd9Sstevel@tonic-gate long sff; 688*7c478bd9Sstevel@tonic-gate { 689*7c478bd9Sstevel@tonic-gate #if !NOFTRUNCATE 690*7c478bd9Sstevel@tonic-gate bool truncate; 691*7c478bd9Sstevel@tonic-gate #endif /* !NOFTRUNCATE */ 692*7c478bd9Sstevel@tonic-gate int rval; 693*7c478bd9Sstevel@tonic-gate int fd; 694*7c478bd9Sstevel@tonic-gate int smode; 695*7c478bd9Sstevel@tonic-gate struct stat stb; 696*7c478bd9Sstevel@tonic-gate 697*7c478bd9Sstevel@tonic-gate if (tTd(44, 10)) 698*7c478bd9Sstevel@tonic-gate sm_dprintf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n", 699*7c478bd9Sstevel@tonic-gate fn, omode, cmode, sff); 700*7c478bd9Sstevel@tonic-gate 701*7c478bd9Sstevel@tonic-gate if (bitset(O_CREAT, omode)) 702*7c478bd9Sstevel@tonic-gate sff |= SFF_CREAT; 703*7c478bd9Sstevel@tonic-gate omode &= ~O_CREAT; 704*7c478bd9Sstevel@tonic-gate smode = 0; 705*7c478bd9Sstevel@tonic-gate switch (omode & O_ACCMODE) 706*7c478bd9Sstevel@tonic-gate { 707*7c478bd9Sstevel@tonic-gate case O_RDONLY: 708*7c478bd9Sstevel@tonic-gate smode = S_IREAD; 709*7c478bd9Sstevel@tonic-gate break; 710*7c478bd9Sstevel@tonic-gate 711*7c478bd9Sstevel@tonic-gate case O_WRONLY: 712*7c478bd9Sstevel@tonic-gate smode = S_IWRITE; 713*7c478bd9Sstevel@tonic-gate break; 714*7c478bd9Sstevel@tonic-gate 715*7c478bd9Sstevel@tonic-gate case O_RDWR: 716*7c478bd9Sstevel@tonic-gate smode = S_IREAD|S_IWRITE; 717*7c478bd9Sstevel@tonic-gate break; 718*7c478bd9Sstevel@tonic-gate 719*7c478bd9Sstevel@tonic-gate default: 720*7c478bd9Sstevel@tonic-gate smode = 0; 721*7c478bd9Sstevel@tonic-gate break; 722*7c478bd9Sstevel@tonic-gate } 723*7c478bd9Sstevel@tonic-gate if (bitset(SFF_OPENASROOT, sff)) 724*7c478bd9Sstevel@tonic-gate rval = safefile(fn, RunAsUid, RunAsGid, RunAsUserName, 725*7c478bd9Sstevel@tonic-gate sff, smode, &stb); 726*7c478bd9Sstevel@tonic-gate else 727*7c478bd9Sstevel@tonic-gate rval = safefile(fn, RealUid, RealGid, RealUserName, 728*7c478bd9Sstevel@tonic-gate sff, smode, &stb); 729*7c478bd9Sstevel@tonic-gate if (rval != 0) 730*7c478bd9Sstevel@tonic-gate { 731*7c478bd9Sstevel@tonic-gate errno = rval; 732*7c478bd9Sstevel@tonic-gate return -1; 733*7c478bd9Sstevel@tonic-gate } 734*7c478bd9Sstevel@tonic-gate if (stb.st_mode == ST_MODE_NOFILE && bitset(SFF_CREAT, sff)) 735*7c478bd9Sstevel@tonic-gate omode |= O_CREAT | (bitset(SFF_NOTEXCL, sff) ? 0 : O_EXCL); 736*7c478bd9Sstevel@tonic-gate else if (bitset(SFF_CREAT, sff) && bitset(O_EXCL, omode)) 737*7c478bd9Sstevel@tonic-gate { 738*7c478bd9Sstevel@tonic-gate /* The file exists so an exclusive create would fail */ 739*7c478bd9Sstevel@tonic-gate errno = EEXIST; 740*7c478bd9Sstevel@tonic-gate return -1; 741*7c478bd9Sstevel@tonic-gate } 742*7c478bd9Sstevel@tonic-gate 743*7c478bd9Sstevel@tonic-gate #if !NOFTRUNCATE 744*7c478bd9Sstevel@tonic-gate truncate = bitset(O_TRUNC, omode); 745*7c478bd9Sstevel@tonic-gate if (truncate) 746*7c478bd9Sstevel@tonic-gate omode &= ~O_TRUNC; 747*7c478bd9Sstevel@tonic-gate #endif /* !NOFTRUNCATE */ 748*7c478bd9Sstevel@tonic-gate 749*7c478bd9Sstevel@tonic-gate fd = dfopen(fn, omode, cmode, sff); 750*7c478bd9Sstevel@tonic-gate if (fd < 0) 751*7c478bd9Sstevel@tonic-gate return fd; 752*7c478bd9Sstevel@tonic-gate if (filechanged(fn, fd, &stb)) 753*7c478bd9Sstevel@tonic-gate { 754*7c478bd9Sstevel@tonic-gate syserr("554 5.3.0 cannot open: file %s changed after open", fn); 755*7c478bd9Sstevel@tonic-gate (void) close(fd); 756*7c478bd9Sstevel@tonic-gate errno = E_SM_FILECHANGE; 757*7c478bd9Sstevel@tonic-gate return -1; 758*7c478bd9Sstevel@tonic-gate } 759*7c478bd9Sstevel@tonic-gate 760*7c478bd9Sstevel@tonic-gate #if !NOFTRUNCATE 761*7c478bd9Sstevel@tonic-gate if (truncate && 762*7c478bd9Sstevel@tonic-gate ftruncate(fd, (off_t) 0) < 0) 763*7c478bd9Sstevel@tonic-gate { 764*7c478bd9Sstevel@tonic-gate int save_errno; 765*7c478bd9Sstevel@tonic-gate 766*7c478bd9Sstevel@tonic-gate save_errno = errno; 767*7c478bd9Sstevel@tonic-gate syserr("554 5.3.0 cannot open: file %s could not be truncated", 768*7c478bd9Sstevel@tonic-gate fn); 769*7c478bd9Sstevel@tonic-gate (void) close(fd); 770*7c478bd9Sstevel@tonic-gate errno = save_errno; 771*7c478bd9Sstevel@tonic-gate return -1; 772*7c478bd9Sstevel@tonic-gate } 773*7c478bd9Sstevel@tonic-gate #endif /* !NOFTRUNCATE */ 774*7c478bd9Sstevel@tonic-gate 775*7c478bd9Sstevel@tonic-gate return fd; 776*7c478bd9Sstevel@tonic-gate } 777*7c478bd9Sstevel@tonic-gate /* 778*7c478bd9Sstevel@tonic-gate ** SAFEFOPEN -- do a file open with extra checking 779*7c478bd9Sstevel@tonic-gate ** 780*7c478bd9Sstevel@tonic-gate ** Parameters: 781*7c478bd9Sstevel@tonic-gate ** fn -- the file name to open. 782*7c478bd9Sstevel@tonic-gate ** omode -- the open-style mode flags. 783*7c478bd9Sstevel@tonic-gate ** cmode -- the create-style mode flags. 784*7c478bd9Sstevel@tonic-gate ** sff -- safefile flags. 785*7c478bd9Sstevel@tonic-gate ** 786*7c478bd9Sstevel@tonic-gate ** Returns: 787*7c478bd9Sstevel@tonic-gate ** Same as fopen. 788*7c478bd9Sstevel@tonic-gate */ 789*7c478bd9Sstevel@tonic-gate 790*7c478bd9Sstevel@tonic-gate SM_FILE_T * 791*7c478bd9Sstevel@tonic-gate safefopen(fn, omode, cmode, sff) 792*7c478bd9Sstevel@tonic-gate char *fn; 793*7c478bd9Sstevel@tonic-gate int omode; 794*7c478bd9Sstevel@tonic-gate int cmode; 795*7c478bd9Sstevel@tonic-gate long sff; 796*7c478bd9Sstevel@tonic-gate { 797*7c478bd9Sstevel@tonic-gate int fd; 798*7c478bd9Sstevel@tonic-gate int save_errno; 799*7c478bd9Sstevel@tonic-gate SM_FILE_T *fp; 800*7c478bd9Sstevel@tonic-gate int fmode; 801*7c478bd9Sstevel@tonic-gate 802*7c478bd9Sstevel@tonic-gate switch (omode & O_ACCMODE) 803*7c478bd9Sstevel@tonic-gate { 804*7c478bd9Sstevel@tonic-gate case O_RDONLY: 805*7c478bd9Sstevel@tonic-gate fmode = SM_IO_RDONLY; 806*7c478bd9Sstevel@tonic-gate break; 807*7c478bd9Sstevel@tonic-gate 808*7c478bd9Sstevel@tonic-gate case O_WRONLY: 809*7c478bd9Sstevel@tonic-gate if (bitset(O_APPEND, omode)) 810*7c478bd9Sstevel@tonic-gate fmode = SM_IO_APPEND; 811*7c478bd9Sstevel@tonic-gate else 812*7c478bd9Sstevel@tonic-gate fmode = SM_IO_WRONLY; 813*7c478bd9Sstevel@tonic-gate break; 814*7c478bd9Sstevel@tonic-gate 815*7c478bd9Sstevel@tonic-gate case O_RDWR: 816*7c478bd9Sstevel@tonic-gate if (bitset(O_TRUNC, omode)) 817*7c478bd9Sstevel@tonic-gate fmode = SM_IO_RDWRTR; 818*7c478bd9Sstevel@tonic-gate else if (bitset(O_APPEND, omode)) 819*7c478bd9Sstevel@tonic-gate fmode = SM_IO_APPENDRW; 820*7c478bd9Sstevel@tonic-gate else 821*7c478bd9Sstevel@tonic-gate fmode = SM_IO_RDWR; 822*7c478bd9Sstevel@tonic-gate break; 823*7c478bd9Sstevel@tonic-gate 824*7c478bd9Sstevel@tonic-gate default: 825*7c478bd9Sstevel@tonic-gate syserr("554 5.3.5 safefopen: unknown omode %o", omode); 826*7c478bd9Sstevel@tonic-gate fmode = 0; 827*7c478bd9Sstevel@tonic-gate } 828*7c478bd9Sstevel@tonic-gate fd = safeopen(fn, omode, cmode, sff); 829*7c478bd9Sstevel@tonic-gate if (fd < 0) 830*7c478bd9Sstevel@tonic-gate { 831*7c478bd9Sstevel@tonic-gate save_errno = errno; 832*7c478bd9Sstevel@tonic-gate if (tTd(44, 10)) 833*7c478bd9Sstevel@tonic-gate sm_dprintf("safefopen: safeopen failed: %s\n", 834*7c478bd9Sstevel@tonic-gate sm_errstring(errno)); 835*7c478bd9Sstevel@tonic-gate errno = save_errno; 836*7c478bd9Sstevel@tonic-gate return NULL; 837*7c478bd9Sstevel@tonic-gate } 838*7c478bd9Sstevel@tonic-gate fp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, 839*7c478bd9Sstevel@tonic-gate (void *) &fd, fmode, NULL); 840*7c478bd9Sstevel@tonic-gate if (fp != NULL) 841*7c478bd9Sstevel@tonic-gate return fp; 842*7c478bd9Sstevel@tonic-gate 843*7c478bd9Sstevel@tonic-gate save_errno = errno; 844*7c478bd9Sstevel@tonic-gate if (tTd(44, 10)) 845*7c478bd9Sstevel@tonic-gate { 846*7c478bd9Sstevel@tonic-gate sm_dprintf("safefopen: fdopen(%s, %d) failed: omode=%x, sff=%lx, err=%s\n", 847*7c478bd9Sstevel@tonic-gate fn, fmode, omode, sff, sm_errstring(errno)); 848*7c478bd9Sstevel@tonic-gate } 849*7c478bd9Sstevel@tonic-gate (void) close(fd); 850*7c478bd9Sstevel@tonic-gate errno = save_errno; 851*7c478bd9Sstevel@tonic-gate return NULL; 852*7c478bd9Sstevel@tonic-gate } 853*7c478bd9Sstevel@tonic-gate /* 854*7c478bd9Sstevel@tonic-gate ** FILECHANGED -- check to see if file changed after being opened 855*7c478bd9Sstevel@tonic-gate ** 856*7c478bd9Sstevel@tonic-gate ** Parameters: 857*7c478bd9Sstevel@tonic-gate ** fn -- pathname of file to check. 858*7c478bd9Sstevel@tonic-gate ** fd -- file descriptor to check. 859*7c478bd9Sstevel@tonic-gate ** stb -- stat structure from before open. 860*7c478bd9Sstevel@tonic-gate ** 861*7c478bd9Sstevel@tonic-gate ** Returns: 862*7c478bd9Sstevel@tonic-gate ** true -- if a problem was detected. 863*7c478bd9Sstevel@tonic-gate ** false -- if this file is still the same. 864*7c478bd9Sstevel@tonic-gate */ 865*7c478bd9Sstevel@tonic-gate 866*7c478bd9Sstevel@tonic-gate bool 867*7c478bd9Sstevel@tonic-gate filechanged(fn, fd, stb) 868*7c478bd9Sstevel@tonic-gate char *fn; 869*7c478bd9Sstevel@tonic-gate int fd; 870*7c478bd9Sstevel@tonic-gate struct stat *stb; 871*7c478bd9Sstevel@tonic-gate { 872*7c478bd9Sstevel@tonic-gate struct stat sta; 873*7c478bd9Sstevel@tonic-gate 874*7c478bd9Sstevel@tonic-gate if (stb->st_mode == ST_MODE_NOFILE) 875*7c478bd9Sstevel@tonic-gate { 876*7c478bd9Sstevel@tonic-gate # if HASLSTAT && BOGUS_O_EXCL 877*7c478bd9Sstevel@tonic-gate /* only necessary if exclusive open follows symbolic links */ 878*7c478bd9Sstevel@tonic-gate if (lstat(fn, stb) < 0 || stb->st_nlink != 1) 879*7c478bd9Sstevel@tonic-gate return true; 880*7c478bd9Sstevel@tonic-gate # else /* HASLSTAT && BOGUS_O_EXCL */ 881*7c478bd9Sstevel@tonic-gate return false; 882*7c478bd9Sstevel@tonic-gate # endif /* HASLSTAT && BOGUS_O_EXCL */ 883*7c478bd9Sstevel@tonic-gate } 884*7c478bd9Sstevel@tonic-gate if (fstat(fd, &sta) < 0) 885*7c478bd9Sstevel@tonic-gate return true; 886*7c478bd9Sstevel@tonic-gate 887*7c478bd9Sstevel@tonic-gate if (sta.st_nlink != stb->st_nlink || 888*7c478bd9Sstevel@tonic-gate sta.st_dev != stb->st_dev || 889*7c478bd9Sstevel@tonic-gate sta.st_ino != stb->st_ino || 890*7c478bd9Sstevel@tonic-gate # if HAS_ST_GEN && 0 /* AFS returns garbage in st_gen */ 891*7c478bd9Sstevel@tonic-gate sta.st_gen != stb->st_gen || 892*7c478bd9Sstevel@tonic-gate # endif /* HAS_ST_GEN && 0 */ 893*7c478bd9Sstevel@tonic-gate sta.st_uid != stb->st_uid || 894*7c478bd9Sstevel@tonic-gate sta.st_gid != stb->st_gid) 895*7c478bd9Sstevel@tonic-gate { 896*7c478bd9Sstevel@tonic-gate if (tTd(44, 8)) 897*7c478bd9Sstevel@tonic-gate { 898*7c478bd9Sstevel@tonic-gate sm_dprintf("File changed after opening:\n"); 899*7c478bd9Sstevel@tonic-gate sm_dprintf(" nlink = %ld/%ld\n", 900*7c478bd9Sstevel@tonic-gate (long) stb->st_nlink, (long) sta.st_nlink); 901*7c478bd9Sstevel@tonic-gate sm_dprintf(" dev = %ld/%ld\n", 902*7c478bd9Sstevel@tonic-gate (long) stb->st_dev, (long) sta.st_dev); 903*7c478bd9Sstevel@tonic-gate sm_dprintf(" ino = %llu/%llu\n", 904*7c478bd9Sstevel@tonic-gate (ULONGLONG_T) stb->st_ino, 905*7c478bd9Sstevel@tonic-gate (ULONGLONG_T) sta.st_ino); 906*7c478bd9Sstevel@tonic-gate # if HAS_ST_GEN 907*7c478bd9Sstevel@tonic-gate sm_dprintf(" gen = %ld/%ld\n", 908*7c478bd9Sstevel@tonic-gate (long) stb->st_gen, (long) sta.st_gen); 909*7c478bd9Sstevel@tonic-gate # endif /* HAS_ST_GEN */ 910*7c478bd9Sstevel@tonic-gate sm_dprintf(" uid = %ld/%ld\n", 911*7c478bd9Sstevel@tonic-gate (long) stb->st_uid, (long) sta.st_uid); 912*7c478bd9Sstevel@tonic-gate sm_dprintf(" gid = %ld/%ld\n", 913*7c478bd9Sstevel@tonic-gate (long) stb->st_gid, (long) sta.st_gid); 914*7c478bd9Sstevel@tonic-gate } 915*7c478bd9Sstevel@tonic-gate return true; 916*7c478bd9Sstevel@tonic-gate } 917*7c478bd9Sstevel@tonic-gate 918*7c478bd9Sstevel@tonic-gate return false; 919*7c478bd9Sstevel@tonic-gate } 920*7c478bd9Sstevel@tonic-gate /* 921*7c478bd9Sstevel@tonic-gate ** DFOPEN -- determined file open 922*7c478bd9Sstevel@tonic-gate ** 923*7c478bd9Sstevel@tonic-gate ** This routine has the semantics of open, except that it will 924*7c478bd9Sstevel@tonic-gate ** keep trying a few times to make this happen. The idea is that 925*7c478bd9Sstevel@tonic-gate ** on very loaded systems, we may run out of resources (inodes, 926*7c478bd9Sstevel@tonic-gate ** whatever), so this tries to get around it. 927*7c478bd9Sstevel@tonic-gate */ 928*7c478bd9Sstevel@tonic-gate 929*7c478bd9Sstevel@tonic-gate int 930*7c478bd9Sstevel@tonic-gate dfopen(filename, omode, cmode, sff) 931*7c478bd9Sstevel@tonic-gate char *filename; 932*7c478bd9Sstevel@tonic-gate int omode; 933*7c478bd9Sstevel@tonic-gate int cmode; 934*7c478bd9Sstevel@tonic-gate long sff; 935*7c478bd9Sstevel@tonic-gate { 936*7c478bd9Sstevel@tonic-gate register int tries; 937*7c478bd9Sstevel@tonic-gate int fd = -1; 938*7c478bd9Sstevel@tonic-gate struct stat st; 939*7c478bd9Sstevel@tonic-gate 940*7c478bd9Sstevel@tonic-gate for (tries = 0; tries < 10; tries++) 941*7c478bd9Sstevel@tonic-gate { 942*7c478bd9Sstevel@tonic-gate (void) sleep((unsigned) (10 * tries)); 943*7c478bd9Sstevel@tonic-gate errno = 0; 944*7c478bd9Sstevel@tonic-gate fd = open(filename, omode, cmode); 945*7c478bd9Sstevel@tonic-gate if (fd >= 0) 946*7c478bd9Sstevel@tonic-gate break; 947*7c478bd9Sstevel@tonic-gate switch (errno) 948*7c478bd9Sstevel@tonic-gate { 949*7c478bd9Sstevel@tonic-gate case ENFILE: /* system file table full */ 950*7c478bd9Sstevel@tonic-gate case EINTR: /* interrupted syscall */ 951*7c478bd9Sstevel@tonic-gate #ifdef ETXTBSY 952*7c478bd9Sstevel@tonic-gate case ETXTBSY: /* Apollo: net file locked */ 953*7c478bd9Sstevel@tonic-gate #endif /* ETXTBSY */ 954*7c478bd9Sstevel@tonic-gate continue; 955*7c478bd9Sstevel@tonic-gate } 956*7c478bd9Sstevel@tonic-gate break; 957*7c478bd9Sstevel@tonic-gate } 958*7c478bd9Sstevel@tonic-gate if (!bitset(SFF_NOLOCK, sff) && 959*7c478bd9Sstevel@tonic-gate fd >= 0 && 960*7c478bd9Sstevel@tonic-gate fstat(fd, &st) >= 0 && 961*7c478bd9Sstevel@tonic-gate S_ISREG(st.st_mode)) 962*7c478bd9Sstevel@tonic-gate { 963*7c478bd9Sstevel@tonic-gate int locktype; 964*7c478bd9Sstevel@tonic-gate 965*7c478bd9Sstevel@tonic-gate /* lock the file to avoid accidental conflicts */ 966*7c478bd9Sstevel@tonic-gate if ((omode & O_ACCMODE) != O_RDONLY) 967*7c478bd9Sstevel@tonic-gate locktype = LOCK_EX; 968*7c478bd9Sstevel@tonic-gate else 969*7c478bd9Sstevel@tonic-gate locktype = LOCK_SH; 970*7c478bd9Sstevel@tonic-gate if (bitset(SFF_NBLOCK, sff)) 971*7c478bd9Sstevel@tonic-gate locktype |= LOCK_NB; 972*7c478bd9Sstevel@tonic-gate 973*7c478bd9Sstevel@tonic-gate if (!lockfile(fd, filename, NULL, locktype)) 974*7c478bd9Sstevel@tonic-gate { 975*7c478bd9Sstevel@tonic-gate int save_errno = errno; 976*7c478bd9Sstevel@tonic-gate 977*7c478bd9Sstevel@tonic-gate (void) close(fd); 978*7c478bd9Sstevel@tonic-gate fd = -1; 979*7c478bd9Sstevel@tonic-gate errno = save_errno; 980*7c478bd9Sstevel@tonic-gate } 981*7c478bd9Sstevel@tonic-gate else 982*7c478bd9Sstevel@tonic-gate errno = 0; 983*7c478bd9Sstevel@tonic-gate } 984*7c478bd9Sstevel@tonic-gate return fd; 985*7c478bd9Sstevel@tonic-gate } 986