17c478bd9Sstevel@tonic-gate /* 27c478bd9Sstevel@tonic-gate * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers. 37c478bd9Sstevel@tonic-gate * All rights reserved. 47c478bd9Sstevel@tonic-gate * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. 57c478bd9Sstevel@tonic-gate * Copyright (c) 1988, 1993 67c478bd9Sstevel@tonic-gate * The Regents of the University of California. All rights reserved. 77c478bd9Sstevel@tonic-gate * 87c478bd9Sstevel@tonic-gate * By using this file, you agree to the terms and conditions set 97c478bd9Sstevel@tonic-gate * forth in the LICENSE file which can be found at the top level of 107c478bd9Sstevel@tonic-gate * the sendmail distribution. 117c478bd9Sstevel@tonic-gate * 127c478bd9Sstevel@tonic-gate */ 137c478bd9Sstevel@tonic-gate 147c478bd9Sstevel@tonic-gate #include <sendmail.h> 157c478bd9Sstevel@tonic-gate #include <sm/io.h> 167c478bd9Sstevel@tonic-gate #include <sm/errstring.h> 177c478bd9Sstevel@tonic-gate 18*e9af4bc0SJohn Beck SM_RCSID("@(#)$Id: safefile.c,v 8.129 2008/08/04 18:07:04 gshapiro Exp $") 197c478bd9Sstevel@tonic-gate 207c478bd9Sstevel@tonic-gate 217c478bd9Sstevel@tonic-gate /* 227c478bd9Sstevel@tonic-gate ** SAFEFILE -- return 0 if a file exists and is safe for a user. 237c478bd9Sstevel@tonic-gate ** 247c478bd9Sstevel@tonic-gate ** Parameters: 257c478bd9Sstevel@tonic-gate ** fn -- filename to check. 267c478bd9Sstevel@tonic-gate ** uid -- user id to compare against. 277c478bd9Sstevel@tonic-gate ** gid -- group id to compare against. 287c478bd9Sstevel@tonic-gate ** user -- user name to compare against (used for group 297c478bd9Sstevel@tonic-gate ** sets). 307c478bd9Sstevel@tonic-gate ** flags -- modifiers: 317c478bd9Sstevel@tonic-gate ** SFF_MUSTOWN -- "uid" must own this file. 327c478bd9Sstevel@tonic-gate ** SFF_NOSLINK -- file cannot be a symbolic link. 337c478bd9Sstevel@tonic-gate ** mode -- mode bits that must match. 347c478bd9Sstevel@tonic-gate ** st -- if set, points to a stat structure that will 357c478bd9Sstevel@tonic-gate ** get the stat info for the file. 367c478bd9Sstevel@tonic-gate ** 377c478bd9Sstevel@tonic-gate ** Returns: 387c478bd9Sstevel@tonic-gate ** 0 if fn exists, is owned by uid, and matches mode. 397c478bd9Sstevel@tonic-gate ** An errno otherwise. The actual errno is cleared. 407c478bd9Sstevel@tonic-gate ** 417c478bd9Sstevel@tonic-gate ** Side Effects: 427c478bd9Sstevel@tonic-gate ** none. 437c478bd9Sstevel@tonic-gate */ 447c478bd9Sstevel@tonic-gate 457c478bd9Sstevel@tonic-gate int 467c478bd9Sstevel@tonic-gate safefile(fn, uid, gid, user, flags, mode, st) 477c478bd9Sstevel@tonic-gate char *fn; 487c478bd9Sstevel@tonic-gate UID_T uid; 497c478bd9Sstevel@tonic-gate GID_T gid; 507c478bd9Sstevel@tonic-gate char *user; 517c478bd9Sstevel@tonic-gate long flags; 527c478bd9Sstevel@tonic-gate int mode; 537c478bd9Sstevel@tonic-gate struct stat *st; 547c478bd9Sstevel@tonic-gate { 557c478bd9Sstevel@tonic-gate register char *p; 567c478bd9Sstevel@tonic-gate register struct group *gr = NULL; 577c478bd9Sstevel@tonic-gate int file_errno = 0; 587c478bd9Sstevel@tonic-gate bool checkpath; 597c478bd9Sstevel@tonic-gate struct stat stbuf; 607c478bd9Sstevel@tonic-gate struct stat fstbuf; 617c478bd9Sstevel@tonic-gate char fbuf[MAXPATHLEN]; 627c478bd9Sstevel@tonic-gate 637c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 647c478bd9Sstevel@tonic-gate sm_dprintf("safefile(%s, uid=%d, gid=%d, flags=%lx, mode=%o):\n", 657c478bd9Sstevel@tonic-gate fn, (int) uid, (int) gid, flags, mode); 667c478bd9Sstevel@tonic-gate errno = 0; 677c478bd9Sstevel@tonic-gate if (sm_strlcpy(fbuf, fn, sizeof fbuf) >= sizeof fbuf) 687c478bd9Sstevel@tonic-gate { 697c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 707c478bd9Sstevel@tonic-gate sm_dprintf("\tpathname too long\n"); 717c478bd9Sstevel@tonic-gate return ENAMETOOLONG; 727c478bd9Sstevel@tonic-gate } 737c478bd9Sstevel@tonic-gate fn = fbuf; 747c478bd9Sstevel@tonic-gate if (st == NULL) 757c478bd9Sstevel@tonic-gate st = &fstbuf; 767c478bd9Sstevel@tonic-gate 777c478bd9Sstevel@tonic-gate /* ignore SFF_SAFEDIRPATH if we are debugging */ 787c478bd9Sstevel@tonic-gate if (RealUid != 0 && RunAsUid == RealUid) 797c478bd9Sstevel@tonic-gate flags &= ~SFF_SAFEDIRPATH; 807c478bd9Sstevel@tonic-gate 817c478bd9Sstevel@tonic-gate /* first check to see if the file exists at all */ 827c478bd9Sstevel@tonic-gate # if HASLSTAT 837c478bd9Sstevel@tonic-gate if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st) 847c478bd9Sstevel@tonic-gate : stat(fn, st)) < 0) 857c478bd9Sstevel@tonic-gate # else /* HASLSTAT */ 867c478bd9Sstevel@tonic-gate if (stat(fn, st) < 0) 877c478bd9Sstevel@tonic-gate # endif /* HASLSTAT */ 887c478bd9Sstevel@tonic-gate { 897c478bd9Sstevel@tonic-gate file_errno = errno; 907c478bd9Sstevel@tonic-gate } 917c478bd9Sstevel@tonic-gate else if (bitset(SFF_SETUIDOK, flags) && 927c478bd9Sstevel@tonic-gate !bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode) && 937c478bd9Sstevel@tonic-gate S_ISREG(st->st_mode)) 947c478bd9Sstevel@tonic-gate { 957c478bd9Sstevel@tonic-gate /* 967c478bd9Sstevel@tonic-gate ** If final file is set-user-ID, run as the owner of that 977c478bd9Sstevel@tonic-gate ** file. Gotta be careful not to reveal anything too 987c478bd9Sstevel@tonic-gate ** soon here! 997c478bd9Sstevel@tonic-gate */ 1007c478bd9Sstevel@tonic-gate 1017c478bd9Sstevel@tonic-gate # ifdef SUID_ROOT_FILES_OK 1027c478bd9Sstevel@tonic-gate if (bitset(S_ISUID, st->st_mode)) 1037c478bd9Sstevel@tonic-gate # else /* SUID_ROOT_FILES_OK */ 1047c478bd9Sstevel@tonic-gate if (bitset(S_ISUID, st->st_mode) && st->st_uid != 0 && 1057c478bd9Sstevel@tonic-gate st->st_uid != TrustedUid) 1067c478bd9Sstevel@tonic-gate # endif /* SUID_ROOT_FILES_OK */ 1077c478bd9Sstevel@tonic-gate { 1087c478bd9Sstevel@tonic-gate uid = st->st_uid; 1097c478bd9Sstevel@tonic-gate user = NULL; 1107c478bd9Sstevel@tonic-gate } 1117c478bd9Sstevel@tonic-gate # ifdef SUID_ROOT_FILES_OK 1127c478bd9Sstevel@tonic-gate if (bitset(S_ISGID, st->st_mode)) 1137c478bd9Sstevel@tonic-gate # else /* SUID_ROOT_FILES_OK */ 1147c478bd9Sstevel@tonic-gate if (bitset(S_ISGID, st->st_mode) && st->st_gid != 0) 1157c478bd9Sstevel@tonic-gate # endif /* SUID_ROOT_FILES_OK */ 1167c478bd9Sstevel@tonic-gate gid = st->st_gid; 1177c478bd9Sstevel@tonic-gate } 1187c478bd9Sstevel@tonic-gate 1197c478bd9Sstevel@tonic-gate checkpath = !bitset(SFF_NOPATHCHECK, flags) || 1207c478bd9Sstevel@tonic-gate (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags)); 1217c478bd9Sstevel@tonic-gate if (bitset(SFF_NOWLINK, flags) && !bitset(SFF_SAFEDIRPATH, flags)) 1227c478bd9Sstevel@tonic-gate { 1237c478bd9Sstevel@tonic-gate int ret; 1247c478bd9Sstevel@tonic-gate 1257c478bd9Sstevel@tonic-gate /* check the directory */ 1267c478bd9Sstevel@tonic-gate p = strrchr(fn, '/'); 1277c478bd9Sstevel@tonic-gate if (p == NULL) 1287c478bd9Sstevel@tonic-gate { 1297c478bd9Sstevel@tonic-gate ret = safedirpath(".", uid, gid, user, 1307c478bd9Sstevel@tonic-gate flags|SFF_SAFEDIRPATH, 0, 0); 1317c478bd9Sstevel@tonic-gate } 1327c478bd9Sstevel@tonic-gate else 1337c478bd9Sstevel@tonic-gate { 1347c478bd9Sstevel@tonic-gate *p = '\0'; 1357c478bd9Sstevel@tonic-gate ret = safedirpath(fn, uid, gid, user, 1367c478bd9Sstevel@tonic-gate flags|SFF_SAFEDIRPATH, 0, 0); 1377c478bd9Sstevel@tonic-gate *p = '/'; 1387c478bd9Sstevel@tonic-gate } 1397c478bd9Sstevel@tonic-gate if (ret == 0) 1407c478bd9Sstevel@tonic-gate { 1417c478bd9Sstevel@tonic-gate /* directory is safe */ 1427c478bd9Sstevel@tonic-gate checkpath = false; 1437c478bd9Sstevel@tonic-gate } 1447c478bd9Sstevel@tonic-gate else 1457c478bd9Sstevel@tonic-gate { 1467c478bd9Sstevel@tonic-gate # if HASLSTAT 1477c478bd9Sstevel@tonic-gate /* Need lstat() information if called stat() before */ 1487c478bd9Sstevel@tonic-gate if (!bitset(SFF_NOSLINK, flags) && lstat(fn, st) < 0) 1497c478bd9Sstevel@tonic-gate { 1507c478bd9Sstevel@tonic-gate ret = errno; 1517c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 1527c478bd9Sstevel@tonic-gate sm_dprintf("\t%s\n", sm_errstring(ret)); 1537c478bd9Sstevel@tonic-gate return ret; 1547c478bd9Sstevel@tonic-gate } 1557c478bd9Sstevel@tonic-gate # endif /* HASLSTAT */ 1567c478bd9Sstevel@tonic-gate /* directory is writable: disallow links */ 1577c478bd9Sstevel@tonic-gate flags |= SFF_NOLINK; 1587c478bd9Sstevel@tonic-gate } 1597c478bd9Sstevel@tonic-gate } 1607c478bd9Sstevel@tonic-gate 1617c478bd9Sstevel@tonic-gate if (checkpath) 1627c478bd9Sstevel@tonic-gate { 1637c478bd9Sstevel@tonic-gate int ret; 1647c478bd9Sstevel@tonic-gate 1657c478bd9Sstevel@tonic-gate p = strrchr(fn, '/'); 1667c478bd9Sstevel@tonic-gate if (p == NULL) 1677c478bd9Sstevel@tonic-gate { 1687c478bd9Sstevel@tonic-gate ret = safedirpath(".", uid, gid, user, flags, 0, 0); 1697c478bd9Sstevel@tonic-gate } 1707c478bd9Sstevel@tonic-gate else 1717c478bd9Sstevel@tonic-gate { 1727c478bd9Sstevel@tonic-gate *p = '\0'; 1737c478bd9Sstevel@tonic-gate ret = safedirpath(fn, uid, gid, user, flags, 0, 0); 1747c478bd9Sstevel@tonic-gate *p = '/'; 1757c478bd9Sstevel@tonic-gate } 1767c478bd9Sstevel@tonic-gate if (ret != 0) 1777c478bd9Sstevel@tonic-gate return ret; 1787c478bd9Sstevel@tonic-gate } 1797c478bd9Sstevel@tonic-gate 1807c478bd9Sstevel@tonic-gate /* 1817c478bd9Sstevel@tonic-gate ** If the target file doesn't exist, check the directory to 1827c478bd9Sstevel@tonic-gate ** ensure that it is writable by this user. 1837c478bd9Sstevel@tonic-gate */ 1847c478bd9Sstevel@tonic-gate 1857c478bd9Sstevel@tonic-gate if (file_errno != 0) 1867c478bd9Sstevel@tonic-gate { 1877c478bd9Sstevel@tonic-gate int ret = file_errno; 1887c478bd9Sstevel@tonic-gate char *dir = fn; 1897c478bd9Sstevel@tonic-gate 1907c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 1917c478bd9Sstevel@tonic-gate sm_dprintf("\t%s\n", sm_errstring(ret)); 1927c478bd9Sstevel@tonic-gate 1937c478bd9Sstevel@tonic-gate errno = 0; 1947c478bd9Sstevel@tonic-gate if (!bitset(SFF_CREAT, flags) || file_errno != ENOENT) 1957c478bd9Sstevel@tonic-gate return ret; 1967c478bd9Sstevel@tonic-gate 1977c478bd9Sstevel@tonic-gate /* check to see if legal to create the file */ 1987c478bd9Sstevel@tonic-gate p = strrchr(dir, '/'); 1997c478bd9Sstevel@tonic-gate if (p == NULL) 2007c478bd9Sstevel@tonic-gate dir = "."; 2017c478bd9Sstevel@tonic-gate else if (p == dir) 2027c478bd9Sstevel@tonic-gate dir = "/"; 2037c478bd9Sstevel@tonic-gate else 2047c478bd9Sstevel@tonic-gate *p = '\0'; 2057c478bd9Sstevel@tonic-gate if (stat(dir, &stbuf) >= 0) 2067c478bd9Sstevel@tonic-gate { 2077c478bd9Sstevel@tonic-gate int md = S_IWRITE|S_IEXEC; 2087c478bd9Sstevel@tonic-gate 2097c478bd9Sstevel@tonic-gate ret = 0; 2107c478bd9Sstevel@tonic-gate if (stbuf.st_uid == uid) 2117c478bd9Sstevel@tonic-gate /* EMPTY */ 2127c478bd9Sstevel@tonic-gate ; 2137c478bd9Sstevel@tonic-gate else if (uid == 0 && stbuf.st_uid == TrustedUid) 2147c478bd9Sstevel@tonic-gate /* EMPTY */ 2157c478bd9Sstevel@tonic-gate ; 2167c478bd9Sstevel@tonic-gate else 2177c478bd9Sstevel@tonic-gate { 2187c478bd9Sstevel@tonic-gate md >>= 3; 2197c478bd9Sstevel@tonic-gate if (stbuf.st_gid == gid) 2207c478bd9Sstevel@tonic-gate /* EMPTY */ 2217c478bd9Sstevel@tonic-gate ; 2227c478bd9Sstevel@tonic-gate # ifndef NO_GROUP_SET 2237c478bd9Sstevel@tonic-gate else if (user != NULL && !DontInitGroups && 2247c478bd9Sstevel@tonic-gate ((gr != NULL && 2257c478bd9Sstevel@tonic-gate gr->gr_gid == stbuf.st_gid) || 2267c478bd9Sstevel@tonic-gate (gr = getgrgid(stbuf.st_gid)) != NULL)) 2277c478bd9Sstevel@tonic-gate { 2287c478bd9Sstevel@tonic-gate register char **gp; 2297c478bd9Sstevel@tonic-gate 2307c478bd9Sstevel@tonic-gate for (gp = gr->gr_mem; *gp != NULL; gp++) 2317c478bd9Sstevel@tonic-gate if (strcmp(*gp, user) == 0) 2327c478bd9Sstevel@tonic-gate break; 2337c478bd9Sstevel@tonic-gate if (*gp == NULL) 2347c478bd9Sstevel@tonic-gate md >>= 3; 2357c478bd9Sstevel@tonic-gate } 2367c478bd9Sstevel@tonic-gate # endif /* ! NO_GROUP_SET */ 2377c478bd9Sstevel@tonic-gate else 2387c478bd9Sstevel@tonic-gate md >>= 3; 2397c478bd9Sstevel@tonic-gate } 2407c478bd9Sstevel@tonic-gate if ((stbuf.st_mode & md) != md) 2417c478bd9Sstevel@tonic-gate ret = errno = EACCES; 2427c478bd9Sstevel@tonic-gate } 2437c478bd9Sstevel@tonic-gate else 2447c478bd9Sstevel@tonic-gate ret = errno; 2457c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 2467c478bd9Sstevel@tonic-gate sm_dprintf("\t[final dir %s uid %d mode %lo] %s\n", 2477c478bd9Sstevel@tonic-gate dir, (int) stbuf.st_uid, 2487c478bd9Sstevel@tonic-gate (unsigned long) stbuf.st_mode, 2497c478bd9Sstevel@tonic-gate sm_errstring(ret)); 2507c478bd9Sstevel@tonic-gate if (p != NULL) 2517c478bd9Sstevel@tonic-gate *p = '/'; 2527c478bd9Sstevel@tonic-gate st->st_mode = ST_MODE_NOFILE; 2537c478bd9Sstevel@tonic-gate return ret; 2547c478bd9Sstevel@tonic-gate } 2557c478bd9Sstevel@tonic-gate 2567c478bd9Sstevel@tonic-gate # ifdef S_ISLNK 2577c478bd9Sstevel@tonic-gate if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode)) 2587c478bd9Sstevel@tonic-gate { 2597c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 2607c478bd9Sstevel@tonic-gate sm_dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n", 2617c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 2627c478bd9Sstevel@tonic-gate return E_SM_NOSLINK; 2637c478bd9Sstevel@tonic-gate } 2647c478bd9Sstevel@tonic-gate # endif /* S_ISLNK */ 2657c478bd9Sstevel@tonic-gate if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode)) 2667c478bd9Sstevel@tonic-gate { 2677c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 2687c478bd9Sstevel@tonic-gate sm_dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n", 2697c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 2707c478bd9Sstevel@tonic-gate return E_SM_REGONLY; 2717c478bd9Sstevel@tonic-gate } 2727c478bd9Sstevel@tonic-gate if (bitset(SFF_NOGWFILES, flags) && 2737c478bd9Sstevel@tonic-gate bitset(S_IWGRP, st->st_mode)) 2747c478bd9Sstevel@tonic-gate { 2757c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 2767c478bd9Sstevel@tonic-gate sm_dprintf("\t[write bits %lo]\tE_SM_GWFILE\n", 2777c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 2787c478bd9Sstevel@tonic-gate return E_SM_GWFILE; 2797c478bd9Sstevel@tonic-gate } 2807c478bd9Sstevel@tonic-gate if (bitset(SFF_NOWWFILES, flags) && 2817c478bd9Sstevel@tonic-gate bitset(S_IWOTH, st->st_mode)) 2827c478bd9Sstevel@tonic-gate { 2837c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 2847c478bd9Sstevel@tonic-gate sm_dprintf("\t[write bits %lo]\tE_SM_WWFILE\n", 2857c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 2867c478bd9Sstevel@tonic-gate return E_SM_WWFILE; 2877c478bd9Sstevel@tonic-gate } 2887c478bd9Sstevel@tonic-gate if (bitset(SFF_NOGRFILES, flags) && bitset(S_IRGRP, st->st_mode)) 2897c478bd9Sstevel@tonic-gate { 2907c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 2917c478bd9Sstevel@tonic-gate sm_dprintf("\t[read bits %lo]\tE_SM_GRFILE\n", 2927c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 2937c478bd9Sstevel@tonic-gate return E_SM_GRFILE; 2947c478bd9Sstevel@tonic-gate } 2957c478bd9Sstevel@tonic-gate if (bitset(SFF_NOWRFILES, flags) && bitset(S_IROTH, st->st_mode)) 2967c478bd9Sstevel@tonic-gate { 2977c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 2987c478bd9Sstevel@tonic-gate sm_dprintf("\t[read bits %lo]\tE_SM_WRFILE\n", 2997c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 3007c478bd9Sstevel@tonic-gate return E_SM_WRFILE; 3017c478bd9Sstevel@tonic-gate } 3027c478bd9Sstevel@tonic-gate if (!bitset(SFF_EXECOK, flags) && 3037c478bd9Sstevel@tonic-gate bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) && 3047c478bd9Sstevel@tonic-gate bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode)) 3057c478bd9Sstevel@tonic-gate { 3067c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 3077c478bd9Sstevel@tonic-gate sm_dprintf("\t[exec bits %lo]\tE_SM_ISEXEC\n", 3087c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode); 3097c478bd9Sstevel@tonic-gate return E_SM_ISEXEC; 3107c478bd9Sstevel@tonic-gate } 3117c478bd9Sstevel@tonic-gate if (bitset(SFF_NOHLINK, flags) && st->st_nlink != 1) 3127c478bd9Sstevel@tonic-gate { 3137c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 3147c478bd9Sstevel@tonic-gate sm_dprintf("\t[link count %d]\tE_SM_NOHLINK\n", 3157c478bd9Sstevel@tonic-gate (int) st->st_nlink); 3167c478bd9Sstevel@tonic-gate return E_SM_NOHLINK; 3177c478bd9Sstevel@tonic-gate } 3187c478bd9Sstevel@tonic-gate 3197c478bd9Sstevel@tonic-gate if (uid == 0 && bitset(SFF_OPENASROOT, flags)) 3207c478bd9Sstevel@tonic-gate /* EMPTY */ 3217c478bd9Sstevel@tonic-gate ; 3227c478bd9Sstevel@tonic-gate else if (uid == 0 && !bitset(SFF_ROOTOK, flags)) 3237c478bd9Sstevel@tonic-gate mode >>= 6; 3247c478bd9Sstevel@tonic-gate else if (st->st_uid == uid) 3257c478bd9Sstevel@tonic-gate /* EMPTY */ 3267c478bd9Sstevel@tonic-gate ; 3277c478bd9Sstevel@tonic-gate else if (uid == 0 && st->st_uid == TrustedUid) 3287c478bd9Sstevel@tonic-gate /* EMPTY */ 3297c478bd9Sstevel@tonic-gate ; 3307c478bd9Sstevel@tonic-gate else 3317c478bd9Sstevel@tonic-gate { 3327c478bd9Sstevel@tonic-gate mode >>= 3; 3337c478bd9Sstevel@tonic-gate if (st->st_gid == gid) 3347c478bd9Sstevel@tonic-gate /* EMPTY */ 3357c478bd9Sstevel@tonic-gate ; 3367c478bd9Sstevel@tonic-gate # ifndef NO_GROUP_SET 3377c478bd9Sstevel@tonic-gate else if (user != NULL && !DontInitGroups && 3387c478bd9Sstevel@tonic-gate ((gr != NULL && gr->gr_gid == st->st_gid) || 3397c478bd9Sstevel@tonic-gate (gr = getgrgid(st->st_gid)) != NULL)) 3407c478bd9Sstevel@tonic-gate { 3417c478bd9Sstevel@tonic-gate register char **gp; 3427c478bd9Sstevel@tonic-gate 3437c478bd9Sstevel@tonic-gate for (gp = gr->gr_mem; *gp != NULL; gp++) 3447c478bd9Sstevel@tonic-gate if (strcmp(*gp, user) == 0) 3457c478bd9Sstevel@tonic-gate break; 3467c478bd9Sstevel@tonic-gate if (*gp == NULL) 3477c478bd9Sstevel@tonic-gate mode >>= 3; 3487c478bd9Sstevel@tonic-gate } 3497c478bd9Sstevel@tonic-gate # endif /* ! NO_GROUP_SET */ 3507c478bd9Sstevel@tonic-gate else 3517c478bd9Sstevel@tonic-gate mode >>= 3; 3527c478bd9Sstevel@tonic-gate } 3537c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 3547c478bd9Sstevel@tonic-gate sm_dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ", 3557c478bd9Sstevel@tonic-gate (int) st->st_uid, (int) st->st_nlink, 3567c478bd9Sstevel@tonic-gate (unsigned long) st->st_mode, (unsigned long) mode); 3577c478bd9Sstevel@tonic-gate if ((st->st_uid == uid || st->st_uid == 0 || 3587c478bd9Sstevel@tonic-gate st->st_uid == TrustedUid || 3597c478bd9Sstevel@tonic-gate !bitset(SFF_MUSTOWN, flags)) && 3607c478bd9Sstevel@tonic-gate (st->st_mode & mode) == mode) 3617c478bd9Sstevel@tonic-gate { 3627c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 3637c478bd9Sstevel@tonic-gate sm_dprintf("\tOK\n"); 3647c478bd9Sstevel@tonic-gate return 0; 3657c478bd9Sstevel@tonic-gate } 3667c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 3677c478bd9Sstevel@tonic-gate sm_dprintf("\tEACCES\n"); 3687c478bd9Sstevel@tonic-gate return EACCES; 3697c478bd9Sstevel@tonic-gate } 3707c478bd9Sstevel@tonic-gate /* 3717c478bd9Sstevel@tonic-gate ** SAFEDIRPATH -- check to make sure a path to a directory is safe 3727c478bd9Sstevel@tonic-gate ** 3737c478bd9Sstevel@tonic-gate ** Safe means not writable and owned by the right folks. 3747c478bd9Sstevel@tonic-gate ** 3757c478bd9Sstevel@tonic-gate ** Parameters: 3767c478bd9Sstevel@tonic-gate ** fn -- filename to check. 3777c478bd9Sstevel@tonic-gate ** uid -- user id to compare against. 3787c478bd9Sstevel@tonic-gate ** gid -- group id to compare against. 3797c478bd9Sstevel@tonic-gate ** user -- user name to compare against (used for group 3807c478bd9Sstevel@tonic-gate ** sets). 3817c478bd9Sstevel@tonic-gate ** flags -- modifiers: 3827c478bd9Sstevel@tonic-gate ** SFF_ROOTOK -- ok to use root permissions to open. 3837c478bd9Sstevel@tonic-gate ** SFF_SAFEDIRPATH -- writable directories are considered 3847c478bd9Sstevel@tonic-gate ** to be fatal errors. 3857c478bd9Sstevel@tonic-gate ** level -- symlink recursive level. 3867c478bd9Sstevel@tonic-gate ** offset -- offset into fn to start checking from. 3877c478bd9Sstevel@tonic-gate ** 3887c478bd9Sstevel@tonic-gate ** Returns: 3897c478bd9Sstevel@tonic-gate ** 0 -- if the directory path is "safe". 3907c478bd9Sstevel@tonic-gate ** else -- an error number associated with the path. 3917c478bd9Sstevel@tonic-gate */ 3927c478bd9Sstevel@tonic-gate 3937c478bd9Sstevel@tonic-gate int 3947c478bd9Sstevel@tonic-gate safedirpath(fn, uid, gid, user, flags, level, offset) 3957c478bd9Sstevel@tonic-gate char *fn; 3967c478bd9Sstevel@tonic-gate UID_T uid; 3977c478bd9Sstevel@tonic-gate GID_T gid; 3987c478bd9Sstevel@tonic-gate char *user; 3997c478bd9Sstevel@tonic-gate long flags; 4007c478bd9Sstevel@tonic-gate int level; 4017c478bd9Sstevel@tonic-gate int offset; 4027c478bd9Sstevel@tonic-gate { 4037c478bd9Sstevel@tonic-gate int ret = 0; 4047c478bd9Sstevel@tonic-gate int mode = S_IWOTH; 4057c478bd9Sstevel@tonic-gate char save = '\0'; 4067c478bd9Sstevel@tonic-gate char *saveptr = NULL; 4077c478bd9Sstevel@tonic-gate char *p, *enddir; 4087c478bd9Sstevel@tonic-gate register struct group *gr = NULL; 4097c478bd9Sstevel@tonic-gate char s[MAXLINKPATHLEN]; 4107c478bd9Sstevel@tonic-gate struct stat stbuf; 4117c478bd9Sstevel@tonic-gate 4127c478bd9Sstevel@tonic-gate /* make sure we aren't in a symlink loop */ 4137c478bd9Sstevel@tonic-gate if (level > MAXSYMLINKS) 4147c478bd9Sstevel@tonic-gate return ELOOP; 4157c478bd9Sstevel@tonic-gate 4167c478bd9Sstevel@tonic-gate if (level < 0 || offset < 0 || offset > strlen(fn)) 4177c478bd9Sstevel@tonic-gate return EINVAL; 4187c478bd9Sstevel@tonic-gate 4197c478bd9Sstevel@tonic-gate /* special case root directory */ 4207c478bd9Sstevel@tonic-gate if (*fn == '\0') 4217c478bd9Sstevel@tonic-gate fn = "/"; 4227c478bd9Sstevel@tonic-gate 4237c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 4247c478bd9Sstevel@tonic-gate sm_dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n", 4257c478bd9Sstevel@tonic-gate fn, (long) uid, (long) gid, flags, level, offset); 4267c478bd9Sstevel@tonic-gate 4277c478bd9Sstevel@tonic-gate if (!bitnset(DBS_GROUPWRITABLEDIRPATHSAFE, DontBlameSendmail)) 4287c478bd9Sstevel@tonic-gate mode |= S_IWGRP; 4297c478bd9Sstevel@tonic-gate 4307c478bd9Sstevel@tonic-gate /* Make a modifiable copy of the filename */ 4317c478bd9Sstevel@tonic-gate if (sm_strlcpy(s, fn, sizeof s) >= sizeof s) 4327c478bd9Sstevel@tonic-gate return EINVAL; 4337c478bd9Sstevel@tonic-gate 4347c478bd9Sstevel@tonic-gate p = s + offset; 4357c478bd9Sstevel@tonic-gate while (p != NULL) 4367c478bd9Sstevel@tonic-gate { 4377c478bd9Sstevel@tonic-gate /* put back character */ 4387c478bd9Sstevel@tonic-gate if (saveptr != NULL) 4397c478bd9Sstevel@tonic-gate { 4407c478bd9Sstevel@tonic-gate *saveptr = save; 4417c478bd9Sstevel@tonic-gate saveptr = NULL; 4427c478bd9Sstevel@tonic-gate p++; 4437c478bd9Sstevel@tonic-gate } 4447c478bd9Sstevel@tonic-gate 4457c478bd9Sstevel@tonic-gate if (*p == '\0') 4467c478bd9Sstevel@tonic-gate break; 4477c478bd9Sstevel@tonic-gate 4487c478bd9Sstevel@tonic-gate p = strchr(p, '/'); 4497c478bd9Sstevel@tonic-gate 4507c478bd9Sstevel@tonic-gate /* Special case for root directory */ 4517c478bd9Sstevel@tonic-gate if (p == s) 4527c478bd9Sstevel@tonic-gate { 4537c478bd9Sstevel@tonic-gate save = *(p + 1); 4547c478bd9Sstevel@tonic-gate saveptr = p + 1; 4557c478bd9Sstevel@tonic-gate *(p + 1) = '\0'; 4567c478bd9Sstevel@tonic-gate } 4577c478bd9Sstevel@tonic-gate else if (p != NULL) 4587c478bd9Sstevel@tonic-gate { 4597c478bd9Sstevel@tonic-gate save = *p; 4607c478bd9Sstevel@tonic-gate saveptr = p; 4617c478bd9Sstevel@tonic-gate *p = '\0'; 4627c478bd9Sstevel@tonic-gate } 4637c478bd9Sstevel@tonic-gate 4647c478bd9Sstevel@tonic-gate /* Heuristic: . and .. have already been checked */ 4657c478bd9Sstevel@tonic-gate enddir = strrchr(s, '/'); 4667c478bd9Sstevel@tonic-gate if (enddir != NULL && 4677c478bd9Sstevel@tonic-gate (strcmp(enddir, "/..") == 0 || 4687c478bd9Sstevel@tonic-gate strcmp(enddir, "/.") == 0)) 4697c478bd9Sstevel@tonic-gate continue; 4707c478bd9Sstevel@tonic-gate 4717c478bd9Sstevel@tonic-gate if (tTd(44, 20)) 4727c478bd9Sstevel@tonic-gate sm_dprintf("\t[dir %s]\n", s); 4737c478bd9Sstevel@tonic-gate 4747c478bd9Sstevel@tonic-gate # if HASLSTAT 4757c478bd9Sstevel@tonic-gate ret = lstat(s, &stbuf); 4767c478bd9Sstevel@tonic-gate # else /* HASLSTAT */ 4777c478bd9Sstevel@tonic-gate ret = stat(s, &stbuf); 4787c478bd9Sstevel@tonic-gate # endif /* HASLSTAT */ 4797c478bd9Sstevel@tonic-gate if (ret < 0) 4807c478bd9Sstevel@tonic-gate { 4817c478bd9Sstevel@tonic-gate ret = errno; 4827c478bd9Sstevel@tonic-gate break; 4837c478bd9Sstevel@tonic-gate } 4847c478bd9Sstevel@tonic-gate 4857c478bd9Sstevel@tonic-gate # ifdef S_ISLNK 4867c478bd9Sstevel@tonic-gate /* Follow symlinks */ 4877c478bd9Sstevel@tonic-gate if (S_ISLNK(stbuf.st_mode)) 4887c478bd9Sstevel@tonic-gate { 4897c478bd9Sstevel@tonic-gate int linklen; 4907c478bd9Sstevel@tonic-gate char *target; 4917c478bd9Sstevel@tonic-gate char buf[MAXPATHLEN]; 4927c478bd9Sstevel@tonic-gate char fullbuf[MAXLINKPATHLEN]; 4937c478bd9Sstevel@tonic-gate 4947c478bd9Sstevel@tonic-gate memset(buf, '\0', sizeof buf); 4957c478bd9Sstevel@tonic-gate linklen = readlink(s, buf, sizeof buf); 4967c478bd9Sstevel@tonic-gate if (linklen < 0) 4977c478bd9Sstevel@tonic-gate { 4987c478bd9Sstevel@tonic-gate ret = errno; 4997c478bd9Sstevel@tonic-gate break; 5007c478bd9Sstevel@tonic-gate } 5017c478bd9Sstevel@tonic-gate if (linklen >= sizeof buf) 5027c478bd9Sstevel@tonic-gate { 5037c478bd9Sstevel@tonic-gate /* file name too long for buffer */ 5047c478bd9Sstevel@tonic-gate ret = errno = EINVAL; 5057c478bd9Sstevel@tonic-gate break; 5067c478bd9Sstevel@tonic-gate } 5077c478bd9Sstevel@tonic-gate 5087c478bd9Sstevel@tonic-gate offset = 0; 5097c478bd9Sstevel@tonic-gate if (*buf == '/') 5107c478bd9Sstevel@tonic-gate { 5117c478bd9Sstevel@tonic-gate target = buf; 5127c478bd9Sstevel@tonic-gate 5137c478bd9Sstevel@tonic-gate /* If path is the same, avoid rechecks */ 5147c478bd9Sstevel@tonic-gate while (s[offset] == buf[offset] && 5157c478bd9Sstevel@tonic-gate s[offset] != '\0') 5167c478bd9Sstevel@tonic-gate offset++; 5177c478bd9Sstevel@tonic-gate 5187c478bd9Sstevel@tonic-gate if (s[offset] == '\0' && buf[offset] == '\0') 5197c478bd9Sstevel@tonic-gate { 5207c478bd9Sstevel@tonic-gate /* strings match, symlink loop */ 5217c478bd9Sstevel@tonic-gate return ELOOP; 5227c478bd9Sstevel@tonic-gate } 5237c478bd9Sstevel@tonic-gate 5247c478bd9Sstevel@tonic-gate /* back off from the mismatch */ 5257c478bd9Sstevel@tonic-gate if (offset > 0) 5267c478bd9Sstevel@tonic-gate offset--; 5277c478bd9Sstevel@tonic-gate 5287c478bd9Sstevel@tonic-gate /* Make sure we are at a directory break */ 5297c478bd9Sstevel@tonic-gate if (offset > 0 && 5307c478bd9Sstevel@tonic-gate s[offset] != '/' && 5317c478bd9Sstevel@tonic-gate s[offset] != '\0') 5327c478bd9Sstevel@tonic-gate { 5337c478bd9Sstevel@tonic-gate while (buf[offset] != '/' && 5347c478bd9Sstevel@tonic-gate offset > 0) 5357c478bd9Sstevel@tonic-gate offset--; 5367c478bd9Sstevel@tonic-gate } 5377c478bd9Sstevel@tonic-gate if (offset > 0 && 5387c478bd9Sstevel@tonic-gate s[offset] == '/' && 5397c478bd9Sstevel@tonic-gate buf[offset] == '/') 5407c478bd9Sstevel@tonic-gate { 5417c478bd9Sstevel@tonic-gate /* Include the trailing slash */ 5427c478bd9Sstevel@tonic-gate offset++; 5437c478bd9Sstevel@tonic-gate } 5447c478bd9Sstevel@tonic-gate } 5457c478bd9Sstevel@tonic-gate else 5467c478bd9Sstevel@tonic-gate { 5477c478bd9Sstevel@tonic-gate char *sptr; 5487c478bd9Sstevel@tonic-gate 5497c478bd9Sstevel@tonic-gate sptr = strrchr(s, '/'); 5507c478bd9Sstevel@tonic-gate if (sptr != NULL) 5517c478bd9Sstevel@tonic-gate { 5527c478bd9Sstevel@tonic-gate *sptr = '\0'; 5537c478bd9Sstevel@tonic-gate offset = sptr + 1 - s; 5547c478bd9Sstevel@tonic-gate if (sm_strlcpyn(fullbuf, 5557c478bd9Sstevel@tonic-gate sizeof fullbuf, 2, 5567c478bd9Sstevel@tonic-gate s, "/") >= 5577c478bd9Sstevel@tonic-gate sizeof fullbuf || 5587c478bd9Sstevel@tonic-gate sm_strlcat(fullbuf, buf, 5597c478bd9Sstevel@tonic-gate sizeof fullbuf) >= 5607c478bd9Sstevel@tonic-gate sizeof fullbuf) 5617c478bd9Sstevel@tonic-gate { 5627c478bd9Sstevel@tonic-gate ret = EINVAL; 5637c478bd9Sstevel@tonic-gate break; 5647c478bd9Sstevel@tonic-gate } 5657c478bd9Sstevel@tonic-gate *sptr = '/'; 5667c478bd9Sstevel@tonic-gate } 5677c478bd9Sstevel@tonic-gate else 5687c478bd9Sstevel@tonic-gate { 5697c478bd9Sstevel@tonic-gate if (sm_strlcpy(fullbuf, buf, 5707c478bd9Sstevel@tonic-gate sizeof fullbuf) >= 5717c478bd9Sstevel@tonic-gate sizeof fullbuf) 5727c478bd9Sstevel@tonic-gate { 5737c478bd9Sstevel@tonic-gate ret = EINVAL; 5747c478bd9Sstevel@tonic-gate break; 5757c478bd9Sstevel@tonic-gate } 5767c478bd9Sstevel@tonic-gate } 5777c478bd9Sstevel@tonic-gate target = fullbuf; 5787c478bd9Sstevel@tonic-gate } 5797c478bd9Sstevel@tonic-gate ret = safedirpath(target, uid, gid, user, flags, 5807c478bd9Sstevel@tonic-gate level + 1, offset); 5817c478bd9Sstevel@tonic-gate if (ret != 0) 5827c478bd9Sstevel@tonic-gate break; 5837c478bd9Sstevel@tonic-gate 5847c478bd9Sstevel@tonic-gate /* Don't check permissions on the link file itself */ 5857c478bd9Sstevel@tonic-gate continue; 5867c478bd9Sstevel@tonic-gate } 5877c478bd9Sstevel@tonic-gate #endif /* S_ISLNK */ 5887c478bd9Sstevel@tonic-gate 5897c478bd9Sstevel@tonic-gate if ((uid == 0 || bitset(SFF_SAFEDIRPATH, flags)) && 5907c478bd9Sstevel@tonic-gate #ifdef S_ISVTX 5917c478bd9Sstevel@tonic-gate !(bitnset(DBS_TRUSTSTICKYBIT, DontBlameSendmail) && 5927c478bd9Sstevel@tonic-gate bitset(S_ISVTX, stbuf.st_mode)) && 5937c478bd9Sstevel@tonic-gate #endif /* S_ISVTX */ 5947c478bd9Sstevel@tonic-gate bitset(mode, stbuf.st_mode)) 5957c478bd9Sstevel@tonic-gate { 5967c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 5977c478bd9Sstevel@tonic-gate sm_dprintf("\t[dir %s] mode %lo ", 5987c478bd9Sstevel@tonic-gate s, (unsigned long) stbuf.st_mode); 5997c478bd9Sstevel@tonic-gate if (bitset(SFF_SAFEDIRPATH, flags)) 6007c478bd9Sstevel@tonic-gate { 6017c478bd9Sstevel@tonic-gate if (bitset(S_IWOTH, stbuf.st_mode)) 6027c478bd9Sstevel@tonic-gate ret = E_SM_WWDIR; 6037c478bd9Sstevel@tonic-gate else 6047c478bd9Sstevel@tonic-gate ret = E_SM_GWDIR; 6057c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 6067c478bd9Sstevel@tonic-gate sm_dprintf("FATAL\n"); 6077c478bd9Sstevel@tonic-gate break; 6087c478bd9Sstevel@tonic-gate } 6097c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 6107c478bd9Sstevel@tonic-gate sm_dprintf("WARNING\n"); 6117c478bd9Sstevel@tonic-gate if (Verbose > 1) 6127c478bd9Sstevel@tonic-gate message("051 WARNING: %s writable directory %s", 6137c478bd9Sstevel@tonic-gate bitset(S_IWOTH, stbuf.st_mode) 6147c478bd9Sstevel@tonic-gate ? "World" 6157c478bd9Sstevel@tonic-gate : "Group", 6167c478bd9Sstevel@tonic-gate s); 6177c478bd9Sstevel@tonic-gate } 6187c478bd9Sstevel@tonic-gate if (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags)) 6197c478bd9Sstevel@tonic-gate { 6207c478bd9Sstevel@tonic-gate if (bitset(S_IXOTH, stbuf.st_mode)) 6217c478bd9Sstevel@tonic-gate continue; 6227c478bd9Sstevel@tonic-gate ret = EACCES; 6237c478bd9Sstevel@tonic-gate break; 6247c478bd9Sstevel@tonic-gate } 6257c478bd9Sstevel@tonic-gate 6267c478bd9Sstevel@tonic-gate /* 6277c478bd9Sstevel@tonic-gate ** Let OS determine access to file if we are not 6287c478bd9Sstevel@tonic-gate ** running as a privileged user. This allows ACLs 6297c478bd9Sstevel@tonic-gate ** to work. Also, if opening as root, assume we can 6307c478bd9Sstevel@tonic-gate ** scan the directory. 6317c478bd9Sstevel@tonic-gate */ 6327c478bd9Sstevel@tonic-gate if (geteuid() != 0 || bitset(SFF_OPENASROOT, flags)) 6337c478bd9Sstevel@tonic-gate continue; 6347c478bd9Sstevel@tonic-gate 6357c478bd9Sstevel@tonic-gate if (stbuf.st_uid == uid && 6367c478bd9Sstevel@tonic-gate bitset(S_IXUSR, stbuf.st_mode)) 6377c478bd9Sstevel@tonic-gate continue; 6387c478bd9Sstevel@tonic-gate if (stbuf.st_gid == gid && 6397c478bd9Sstevel@tonic-gate bitset(S_IXGRP, stbuf.st_mode)) 6407c478bd9Sstevel@tonic-gate continue; 6417c478bd9Sstevel@tonic-gate # ifndef NO_GROUP_SET 6427c478bd9Sstevel@tonic-gate if (user != NULL && !DontInitGroups && 6437c478bd9Sstevel@tonic-gate ((gr != NULL && gr->gr_gid == stbuf.st_gid) || 6447c478bd9Sstevel@tonic-gate (gr = getgrgid(stbuf.st_gid)) != NULL)) 6457c478bd9Sstevel@tonic-gate { 6467c478bd9Sstevel@tonic-gate register char **gp; 6477c478bd9Sstevel@tonic-gate 6487c478bd9Sstevel@tonic-gate for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++) 6497c478bd9Sstevel@tonic-gate if (strcmp(*gp, user) == 0) 6507c478bd9Sstevel@tonic-gate break; 6517c478bd9Sstevel@tonic-gate if (gp != NULL && *gp != NULL && 6527c478bd9Sstevel@tonic-gate bitset(S_IXGRP, stbuf.st_mode)) 6537c478bd9Sstevel@tonic-gate continue; 6547c478bd9Sstevel@tonic-gate } 6557c478bd9Sstevel@tonic-gate # endif /* ! NO_GROUP_SET */ 6567c478bd9Sstevel@tonic-gate if (!bitset(S_IXOTH, stbuf.st_mode)) 6577c478bd9Sstevel@tonic-gate { 6587c478bd9Sstevel@tonic-gate ret = EACCES; 6597c478bd9Sstevel@tonic-gate break; 6607c478bd9Sstevel@tonic-gate } 6617c478bd9Sstevel@tonic-gate } 6627c478bd9Sstevel@tonic-gate if (tTd(44, 4)) 6637c478bd9Sstevel@tonic-gate sm_dprintf("\t[dir %s] %s\n", fn, 6647c478bd9Sstevel@tonic-gate ret == 0 ? "OK" : sm_errstring(ret)); 6657c478bd9Sstevel@tonic-gate return ret; 6667c478bd9Sstevel@tonic-gate } 6677c478bd9Sstevel@tonic-gate /* 6687c478bd9Sstevel@tonic-gate ** SAFEOPEN -- do a file open with extra checking 6697c478bd9Sstevel@tonic-gate ** 6707c478bd9Sstevel@tonic-gate ** Parameters: 6717c478bd9Sstevel@tonic-gate ** fn -- the file name to open. 6727c478bd9Sstevel@tonic-gate ** omode -- the open-style mode flags. 6737c478bd9Sstevel@tonic-gate ** cmode -- the create-style mode flags. 6747c478bd9Sstevel@tonic-gate ** sff -- safefile flags. 6757c478bd9Sstevel@tonic-gate ** 6767c478bd9Sstevel@tonic-gate ** Returns: 6777c478bd9Sstevel@tonic-gate ** Same as open. 6787c478bd9Sstevel@tonic-gate */ 6797c478bd9Sstevel@tonic-gate 6807c478bd9Sstevel@tonic-gate int 6817c478bd9Sstevel@tonic-gate safeopen(fn, omode, cmode, sff) 6827c478bd9Sstevel@tonic-gate char *fn; 6837c478bd9Sstevel@tonic-gate int omode; 6847c478bd9Sstevel@tonic-gate int cmode; 6857c478bd9Sstevel@tonic-gate long sff; 6867c478bd9Sstevel@tonic-gate { 6877c478bd9Sstevel@tonic-gate #if !NOFTRUNCATE 6887c478bd9Sstevel@tonic-gate bool truncate; 6897c478bd9Sstevel@tonic-gate #endif /* !NOFTRUNCATE */ 6907c478bd9Sstevel@tonic-gate int rval; 6917c478bd9Sstevel@tonic-gate int fd; 6927c478bd9Sstevel@tonic-gate int smode; 6937c478bd9Sstevel@tonic-gate struct stat stb; 6947c478bd9Sstevel@tonic-gate 6957c478bd9Sstevel@tonic-gate if (tTd(44, 10)) 6967c478bd9Sstevel@tonic-gate sm_dprintf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n", 6977c478bd9Sstevel@tonic-gate fn, omode, cmode, sff); 6987c478bd9Sstevel@tonic-gate 6997c478bd9Sstevel@tonic-gate if (bitset(O_CREAT, omode)) 7007c478bd9Sstevel@tonic-gate sff |= SFF_CREAT; 7017c478bd9Sstevel@tonic-gate omode &= ~O_CREAT; 7027c478bd9Sstevel@tonic-gate switch (omode & O_ACCMODE) 7037c478bd9Sstevel@tonic-gate { 7047c478bd9Sstevel@tonic-gate case O_RDONLY: 7057c478bd9Sstevel@tonic-gate smode = S_IREAD; 7067c478bd9Sstevel@tonic-gate break; 7077c478bd9Sstevel@tonic-gate 7087c478bd9Sstevel@tonic-gate case O_WRONLY: 7097c478bd9Sstevel@tonic-gate smode = S_IWRITE; 7107c478bd9Sstevel@tonic-gate break; 7117c478bd9Sstevel@tonic-gate 7127c478bd9Sstevel@tonic-gate case O_RDWR: 7137c478bd9Sstevel@tonic-gate smode = S_IREAD|S_IWRITE; 7147c478bd9Sstevel@tonic-gate break; 7157c478bd9Sstevel@tonic-gate 7167c478bd9Sstevel@tonic-gate default: 7177c478bd9Sstevel@tonic-gate smode = 0; 7187c478bd9Sstevel@tonic-gate break; 7197c478bd9Sstevel@tonic-gate } 7207c478bd9Sstevel@tonic-gate if (bitset(SFF_OPENASROOT, sff)) 7217c478bd9Sstevel@tonic-gate rval = safefile(fn, RunAsUid, RunAsGid, RunAsUserName, 7227c478bd9Sstevel@tonic-gate sff, smode, &stb); 7237c478bd9Sstevel@tonic-gate else 7247c478bd9Sstevel@tonic-gate rval = safefile(fn, RealUid, RealGid, RealUserName, 7257c478bd9Sstevel@tonic-gate sff, smode, &stb); 7267c478bd9Sstevel@tonic-gate if (rval != 0) 7277c478bd9Sstevel@tonic-gate { 7287c478bd9Sstevel@tonic-gate errno = rval; 7297c478bd9Sstevel@tonic-gate return -1; 7307c478bd9Sstevel@tonic-gate } 7317c478bd9Sstevel@tonic-gate if (stb.st_mode == ST_MODE_NOFILE && bitset(SFF_CREAT, sff)) 7327c478bd9Sstevel@tonic-gate omode |= O_CREAT | (bitset(SFF_NOTEXCL, sff) ? 0 : O_EXCL); 7337c478bd9Sstevel@tonic-gate else if (bitset(SFF_CREAT, sff) && bitset(O_EXCL, omode)) 7347c478bd9Sstevel@tonic-gate { 7357c478bd9Sstevel@tonic-gate /* The file exists so an exclusive create would fail */ 7367c478bd9Sstevel@tonic-gate errno = EEXIST; 7377c478bd9Sstevel@tonic-gate return -1; 7387c478bd9Sstevel@tonic-gate } 7397c478bd9Sstevel@tonic-gate 7407c478bd9Sstevel@tonic-gate #if !NOFTRUNCATE 7417c478bd9Sstevel@tonic-gate truncate = bitset(O_TRUNC, omode); 7427c478bd9Sstevel@tonic-gate if (truncate) 7437c478bd9Sstevel@tonic-gate omode &= ~O_TRUNC; 7447c478bd9Sstevel@tonic-gate #endif /* !NOFTRUNCATE */ 7457c478bd9Sstevel@tonic-gate 7467c478bd9Sstevel@tonic-gate fd = dfopen(fn, omode, cmode, sff); 7477c478bd9Sstevel@tonic-gate if (fd < 0) 7487c478bd9Sstevel@tonic-gate return fd; 7497c478bd9Sstevel@tonic-gate if (filechanged(fn, fd, &stb)) 7507c478bd9Sstevel@tonic-gate { 7517c478bd9Sstevel@tonic-gate syserr("554 5.3.0 cannot open: file %s changed after open", fn); 7527c478bd9Sstevel@tonic-gate (void) close(fd); 7537c478bd9Sstevel@tonic-gate errno = E_SM_FILECHANGE; 7547c478bd9Sstevel@tonic-gate return -1; 7557c478bd9Sstevel@tonic-gate } 7567c478bd9Sstevel@tonic-gate 7577c478bd9Sstevel@tonic-gate #if !NOFTRUNCATE 7587c478bd9Sstevel@tonic-gate if (truncate && 7597c478bd9Sstevel@tonic-gate ftruncate(fd, (off_t) 0) < 0) 7607c478bd9Sstevel@tonic-gate { 7617c478bd9Sstevel@tonic-gate int save_errno; 7627c478bd9Sstevel@tonic-gate 7637c478bd9Sstevel@tonic-gate save_errno = errno; 7647c478bd9Sstevel@tonic-gate syserr("554 5.3.0 cannot open: file %s could not be truncated", 7657c478bd9Sstevel@tonic-gate fn); 7667c478bd9Sstevel@tonic-gate (void) close(fd); 7677c478bd9Sstevel@tonic-gate errno = save_errno; 7687c478bd9Sstevel@tonic-gate return -1; 7697c478bd9Sstevel@tonic-gate } 7707c478bd9Sstevel@tonic-gate #endif /* !NOFTRUNCATE */ 7717c478bd9Sstevel@tonic-gate 7727c478bd9Sstevel@tonic-gate return fd; 7737c478bd9Sstevel@tonic-gate } 7747c478bd9Sstevel@tonic-gate /* 7757c478bd9Sstevel@tonic-gate ** SAFEFOPEN -- do a file open with extra checking 7767c478bd9Sstevel@tonic-gate ** 7777c478bd9Sstevel@tonic-gate ** Parameters: 7787c478bd9Sstevel@tonic-gate ** fn -- the file name to open. 7797c478bd9Sstevel@tonic-gate ** omode -- the open-style mode flags. 7807c478bd9Sstevel@tonic-gate ** cmode -- the create-style mode flags. 7817c478bd9Sstevel@tonic-gate ** sff -- safefile flags. 7827c478bd9Sstevel@tonic-gate ** 7837c478bd9Sstevel@tonic-gate ** Returns: 7847c478bd9Sstevel@tonic-gate ** Same as fopen. 7857c478bd9Sstevel@tonic-gate */ 7867c478bd9Sstevel@tonic-gate 7877c478bd9Sstevel@tonic-gate SM_FILE_T * 7887c478bd9Sstevel@tonic-gate safefopen(fn, omode, cmode, sff) 7897c478bd9Sstevel@tonic-gate char *fn; 7907c478bd9Sstevel@tonic-gate int omode; 7917c478bd9Sstevel@tonic-gate int cmode; 7927c478bd9Sstevel@tonic-gate long sff; 7937c478bd9Sstevel@tonic-gate { 7947c478bd9Sstevel@tonic-gate int fd; 7957c478bd9Sstevel@tonic-gate int save_errno; 7967c478bd9Sstevel@tonic-gate SM_FILE_T *fp; 7977c478bd9Sstevel@tonic-gate int fmode; 7987c478bd9Sstevel@tonic-gate 7997c478bd9Sstevel@tonic-gate switch (omode & O_ACCMODE) 8007c478bd9Sstevel@tonic-gate { 8017c478bd9Sstevel@tonic-gate case O_RDONLY: 8027c478bd9Sstevel@tonic-gate fmode = SM_IO_RDONLY; 8037c478bd9Sstevel@tonic-gate break; 8047c478bd9Sstevel@tonic-gate 8057c478bd9Sstevel@tonic-gate case O_WRONLY: 8067c478bd9Sstevel@tonic-gate if (bitset(O_APPEND, omode)) 8077c478bd9Sstevel@tonic-gate fmode = SM_IO_APPEND; 8087c478bd9Sstevel@tonic-gate else 8097c478bd9Sstevel@tonic-gate fmode = SM_IO_WRONLY; 8107c478bd9Sstevel@tonic-gate break; 8117c478bd9Sstevel@tonic-gate 8127c478bd9Sstevel@tonic-gate case O_RDWR: 8137c478bd9Sstevel@tonic-gate if (bitset(O_TRUNC, omode)) 8147c478bd9Sstevel@tonic-gate fmode = SM_IO_RDWRTR; 8157c478bd9Sstevel@tonic-gate else if (bitset(O_APPEND, omode)) 8167c478bd9Sstevel@tonic-gate fmode = SM_IO_APPENDRW; 8177c478bd9Sstevel@tonic-gate else 8187c478bd9Sstevel@tonic-gate fmode = SM_IO_RDWR; 8197c478bd9Sstevel@tonic-gate break; 8207c478bd9Sstevel@tonic-gate 8217c478bd9Sstevel@tonic-gate default: 8227c478bd9Sstevel@tonic-gate syserr("554 5.3.5 safefopen: unknown omode %o", omode); 8237c478bd9Sstevel@tonic-gate fmode = 0; 8247c478bd9Sstevel@tonic-gate } 8257c478bd9Sstevel@tonic-gate fd = safeopen(fn, omode, cmode, sff); 8267c478bd9Sstevel@tonic-gate if (fd < 0) 8277c478bd9Sstevel@tonic-gate { 8287c478bd9Sstevel@tonic-gate save_errno = errno; 8297c478bd9Sstevel@tonic-gate if (tTd(44, 10)) 8307c478bd9Sstevel@tonic-gate sm_dprintf("safefopen: safeopen failed: %s\n", 8317c478bd9Sstevel@tonic-gate sm_errstring(errno)); 8327c478bd9Sstevel@tonic-gate errno = save_errno; 8337c478bd9Sstevel@tonic-gate return NULL; 8347c478bd9Sstevel@tonic-gate } 8357c478bd9Sstevel@tonic-gate fp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, 8367c478bd9Sstevel@tonic-gate (void *) &fd, fmode, NULL); 8377c478bd9Sstevel@tonic-gate if (fp != NULL) 8387c478bd9Sstevel@tonic-gate return fp; 8397c478bd9Sstevel@tonic-gate 8407c478bd9Sstevel@tonic-gate save_errno = errno; 8417c478bd9Sstevel@tonic-gate if (tTd(44, 10)) 8427c478bd9Sstevel@tonic-gate { 8437c478bd9Sstevel@tonic-gate sm_dprintf("safefopen: fdopen(%s, %d) failed: omode=%x, sff=%lx, err=%s\n", 8447c478bd9Sstevel@tonic-gate fn, fmode, omode, sff, sm_errstring(errno)); 8457c478bd9Sstevel@tonic-gate } 8467c478bd9Sstevel@tonic-gate (void) close(fd); 8477c478bd9Sstevel@tonic-gate errno = save_errno; 8487c478bd9Sstevel@tonic-gate return NULL; 8497c478bd9Sstevel@tonic-gate } 8507c478bd9Sstevel@tonic-gate /* 8517c478bd9Sstevel@tonic-gate ** FILECHANGED -- check to see if file changed after being opened 8527c478bd9Sstevel@tonic-gate ** 8537c478bd9Sstevel@tonic-gate ** Parameters: 8547c478bd9Sstevel@tonic-gate ** fn -- pathname of file to check. 8557c478bd9Sstevel@tonic-gate ** fd -- file descriptor to check. 8567c478bd9Sstevel@tonic-gate ** stb -- stat structure from before open. 8577c478bd9Sstevel@tonic-gate ** 8587c478bd9Sstevel@tonic-gate ** Returns: 8597c478bd9Sstevel@tonic-gate ** true -- if a problem was detected. 8607c478bd9Sstevel@tonic-gate ** false -- if this file is still the same. 8617c478bd9Sstevel@tonic-gate */ 8627c478bd9Sstevel@tonic-gate 8637c478bd9Sstevel@tonic-gate bool 8647c478bd9Sstevel@tonic-gate filechanged(fn, fd, stb) 8657c478bd9Sstevel@tonic-gate char *fn; 8667c478bd9Sstevel@tonic-gate int fd; 8677c478bd9Sstevel@tonic-gate struct stat *stb; 8687c478bd9Sstevel@tonic-gate { 8697c478bd9Sstevel@tonic-gate struct stat sta; 8707c478bd9Sstevel@tonic-gate 8717c478bd9Sstevel@tonic-gate if (stb->st_mode == ST_MODE_NOFILE) 8727c478bd9Sstevel@tonic-gate { 8737c478bd9Sstevel@tonic-gate # if HASLSTAT && BOGUS_O_EXCL 8747c478bd9Sstevel@tonic-gate /* only necessary if exclusive open follows symbolic links */ 8757c478bd9Sstevel@tonic-gate if (lstat(fn, stb) < 0 || stb->st_nlink != 1) 8767c478bd9Sstevel@tonic-gate return true; 8777c478bd9Sstevel@tonic-gate # else /* HASLSTAT && BOGUS_O_EXCL */ 8787c478bd9Sstevel@tonic-gate return false; 8797c478bd9Sstevel@tonic-gate # endif /* HASLSTAT && BOGUS_O_EXCL */ 8807c478bd9Sstevel@tonic-gate } 8817c478bd9Sstevel@tonic-gate if (fstat(fd, &sta) < 0) 8827c478bd9Sstevel@tonic-gate return true; 8837c478bd9Sstevel@tonic-gate 8847c478bd9Sstevel@tonic-gate if (sta.st_nlink != stb->st_nlink || 8857c478bd9Sstevel@tonic-gate sta.st_dev != stb->st_dev || 8867c478bd9Sstevel@tonic-gate sta.st_ino != stb->st_ino || 8877c478bd9Sstevel@tonic-gate # if HAS_ST_GEN && 0 /* AFS returns garbage in st_gen */ 8887c478bd9Sstevel@tonic-gate sta.st_gen != stb->st_gen || 8897c478bd9Sstevel@tonic-gate # endif /* HAS_ST_GEN && 0 */ 8907c478bd9Sstevel@tonic-gate sta.st_uid != stb->st_uid || 8917c478bd9Sstevel@tonic-gate sta.st_gid != stb->st_gid) 8927c478bd9Sstevel@tonic-gate { 8937c478bd9Sstevel@tonic-gate if (tTd(44, 8)) 8947c478bd9Sstevel@tonic-gate { 8957c478bd9Sstevel@tonic-gate sm_dprintf("File changed after opening:\n"); 8967c478bd9Sstevel@tonic-gate sm_dprintf(" nlink = %ld/%ld\n", 8977c478bd9Sstevel@tonic-gate (long) stb->st_nlink, (long) sta.st_nlink); 8987c478bd9Sstevel@tonic-gate sm_dprintf(" dev = %ld/%ld\n", 8997c478bd9Sstevel@tonic-gate (long) stb->st_dev, (long) sta.st_dev); 9007c478bd9Sstevel@tonic-gate sm_dprintf(" ino = %llu/%llu\n", 9017c478bd9Sstevel@tonic-gate (ULONGLONG_T) stb->st_ino, 9027c478bd9Sstevel@tonic-gate (ULONGLONG_T) sta.st_ino); 9037c478bd9Sstevel@tonic-gate # if HAS_ST_GEN 9047c478bd9Sstevel@tonic-gate sm_dprintf(" gen = %ld/%ld\n", 9057c478bd9Sstevel@tonic-gate (long) stb->st_gen, (long) sta.st_gen); 9067c478bd9Sstevel@tonic-gate # endif /* HAS_ST_GEN */ 9077c478bd9Sstevel@tonic-gate sm_dprintf(" uid = %ld/%ld\n", 9087c478bd9Sstevel@tonic-gate (long) stb->st_uid, (long) sta.st_uid); 9097c478bd9Sstevel@tonic-gate sm_dprintf(" gid = %ld/%ld\n", 9107c478bd9Sstevel@tonic-gate (long) stb->st_gid, (long) sta.st_gid); 9117c478bd9Sstevel@tonic-gate } 9127c478bd9Sstevel@tonic-gate return true; 9137c478bd9Sstevel@tonic-gate } 9147c478bd9Sstevel@tonic-gate 9157c478bd9Sstevel@tonic-gate return false; 9167c478bd9Sstevel@tonic-gate } 9177c478bd9Sstevel@tonic-gate /* 9187c478bd9Sstevel@tonic-gate ** DFOPEN -- determined file open 9197c478bd9Sstevel@tonic-gate ** 9207c478bd9Sstevel@tonic-gate ** This routine has the semantics of open, except that it will 9217c478bd9Sstevel@tonic-gate ** keep trying a few times to make this happen. The idea is that 9227c478bd9Sstevel@tonic-gate ** on very loaded systems, we may run out of resources (inodes, 9237c478bd9Sstevel@tonic-gate ** whatever), so this tries to get around it. 9247c478bd9Sstevel@tonic-gate */ 9257c478bd9Sstevel@tonic-gate 9267c478bd9Sstevel@tonic-gate int 9277c478bd9Sstevel@tonic-gate dfopen(filename, omode, cmode, sff) 9287c478bd9Sstevel@tonic-gate char *filename; 9297c478bd9Sstevel@tonic-gate int omode; 9307c478bd9Sstevel@tonic-gate int cmode; 9317c478bd9Sstevel@tonic-gate long sff; 9327c478bd9Sstevel@tonic-gate { 9337c478bd9Sstevel@tonic-gate register int tries; 9347c478bd9Sstevel@tonic-gate int fd = -1; 9357c478bd9Sstevel@tonic-gate struct stat st; 9367c478bd9Sstevel@tonic-gate 9377c478bd9Sstevel@tonic-gate for (tries = 0; tries < 10; tries++) 9387c478bd9Sstevel@tonic-gate { 9397c478bd9Sstevel@tonic-gate (void) sleep((unsigned) (10 * tries)); 9407c478bd9Sstevel@tonic-gate errno = 0; 9417c478bd9Sstevel@tonic-gate fd = open(filename, omode, cmode); 9427c478bd9Sstevel@tonic-gate if (fd >= 0) 9437c478bd9Sstevel@tonic-gate break; 9447c478bd9Sstevel@tonic-gate switch (errno) 9457c478bd9Sstevel@tonic-gate { 9467c478bd9Sstevel@tonic-gate case ENFILE: /* system file table full */ 9477c478bd9Sstevel@tonic-gate case EINTR: /* interrupted syscall */ 9487c478bd9Sstevel@tonic-gate #ifdef ETXTBSY 9497c478bd9Sstevel@tonic-gate case ETXTBSY: /* Apollo: net file locked */ 9507c478bd9Sstevel@tonic-gate #endif /* ETXTBSY */ 9517c478bd9Sstevel@tonic-gate continue; 9527c478bd9Sstevel@tonic-gate } 9537c478bd9Sstevel@tonic-gate break; 9547c478bd9Sstevel@tonic-gate } 9557c478bd9Sstevel@tonic-gate if (!bitset(SFF_NOLOCK, sff) && 9567c478bd9Sstevel@tonic-gate fd >= 0 && 9577c478bd9Sstevel@tonic-gate fstat(fd, &st) >= 0 && 9587c478bd9Sstevel@tonic-gate S_ISREG(st.st_mode)) 9597c478bd9Sstevel@tonic-gate { 9607c478bd9Sstevel@tonic-gate int locktype; 9617c478bd9Sstevel@tonic-gate 9627c478bd9Sstevel@tonic-gate /* lock the file to avoid accidental conflicts */ 9637c478bd9Sstevel@tonic-gate if ((omode & O_ACCMODE) != O_RDONLY) 9647c478bd9Sstevel@tonic-gate locktype = LOCK_EX; 9657c478bd9Sstevel@tonic-gate else 9667c478bd9Sstevel@tonic-gate locktype = LOCK_SH; 9677c478bd9Sstevel@tonic-gate if (bitset(SFF_NBLOCK, sff)) 9687c478bd9Sstevel@tonic-gate locktype |= LOCK_NB; 9697c478bd9Sstevel@tonic-gate 9707c478bd9Sstevel@tonic-gate if (!lockfile(fd, filename, NULL, locktype)) 9717c478bd9Sstevel@tonic-gate { 9727c478bd9Sstevel@tonic-gate int save_errno = errno; 9737c478bd9Sstevel@tonic-gate 9747c478bd9Sstevel@tonic-gate (void) close(fd); 9757c478bd9Sstevel@tonic-gate fd = -1; 9767c478bd9Sstevel@tonic-gate errno = save_errno; 9777c478bd9Sstevel@tonic-gate } 9787c478bd9Sstevel@tonic-gate else 9797c478bd9Sstevel@tonic-gate errno = 0; 9807c478bd9Sstevel@tonic-gate } 9817c478bd9Sstevel@tonic-gate return fd; 9827c478bd9Sstevel@tonic-gate } 983