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