]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/sendmail/libsmutil/safefile.c
Import of sendmail version 8.11.1 into vendor branch SENDMAIL with
[FreeBSD/FreeBSD.git] / contrib / sendmail / libsmutil / safefile.c
1 /*
2  * Copyright (c) 1998-2000 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 #ifndef lint
15 static char id[] = "@(#)$Id: safefile.c,v 8.81.4.7 2000/09/01 21:09:23 ca Exp $";
16 #endif /* ! lint */
17
18 #include <sendmail.h>
19
20
21 \f/*
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 + 1];
62
63         if (tTd(44, 4))
64                 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 (st == NULL)
68                 st = &fstbuf;
69         if (strlcpy(fbuf, fn, sizeof fbuf) >= sizeof fbuf)
70         {
71                 if (tTd(44, 4))
72                         dprintf("\tpathname too long\n");
73                 return ENAMETOOLONG;
74         }
75         fn = fbuf;
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 setuid, 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                                         dprintf("\t%s\n", 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                         dprintf("\t%s\n", 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                         if (stbuf.st_uid == uid)
210                                 /* EMPTY */
211                                 ;
212                         else if (uid == 0 && stbuf.st_uid == TrustedUid)
213                                 /* EMPTY */
214                                 ;
215                         else
216                         {
217                                 md >>= 3;
218                                 if (stbuf.st_gid == gid)
219                                         /* EMPTY */
220                                         ;
221 # ifndef NO_GROUP_SET
222                                 else if (user != NULL && !DontInitGroups &&
223                                          ((gr != NULL &&
224                                            gr->gr_gid == stbuf.st_gid) ||
225                                           (gr = getgrgid(stbuf.st_gid)) != NULL))
226                                 {
227                                         register char **gp;
228
229                                         for (gp = gr->gr_mem; *gp != NULL; gp++)
230                                                 if (strcmp(*gp, user) == 0)
231                                                         break;
232                                         if (*gp == NULL)
233                                                 md >>= 3;
234                                 }
235 # endif /* ! NO_GROUP_SET */
236                                 else
237                                         md >>= 3;
238                         }
239                         if ((stbuf.st_mode & md) != md)
240                                 errno = EACCES;
241                 }
242                 ret = errno;
243                 if (tTd(44, 4))
244                         dprintf("\t[final dir %s uid %d mode %lo] %s\n",
245                                 dir, (int) stbuf.st_uid, (u_long) stbuf.st_mode,
246                                 errstring(ret));
247                 if (p != NULL)
248                         *p = '/';
249                 st->st_mode = ST_MODE_NOFILE;
250                 return ret;
251         }
252
253 # ifdef S_ISLNK
254         if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode))
255         {
256                 if (tTd(44, 4))
257                         dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n",
258                                 (u_long) st->st_mode);
259                 return E_SM_NOSLINK;
260         }
261 # endif /* S_ISLNK */
262         if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode))
263         {
264                 if (tTd(44, 4))
265                         dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n",
266                                 (u_long) st->st_mode);
267                 return E_SM_REGONLY;
268         }
269         if (bitset(SFF_NOGWFILES, flags) &&
270             bitset(S_IWGRP, st->st_mode))
271         {
272                 if (tTd(44, 4))
273                         dprintf("\t[write bits %lo]\tE_SM_GWFILE\n",
274                                 (u_long) st->st_mode);
275                 return E_SM_GWFILE;
276         }
277         if (bitset(SFF_NOWWFILES, flags) &&
278             bitset(S_IWOTH, st->st_mode))
279         {
280                 if (tTd(44, 4))
281                         dprintf("\t[write bits %lo]\tE_SM_WWFILE\n",
282                                 (u_long) st->st_mode);
283                 return E_SM_WWFILE;
284         }
285         if (bitset(SFF_NOGRFILES, flags) && bitset(S_IRGRP, st->st_mode))
286         {
287                 if (tTd(44, 4))
288                         dprintf("\t[read bits %lo]\tE_SM_GRFILE\n",
289                                 (u_long) st->st_mode);
290                 return E_SM_GRFILE;
291         }
292         if (bitset(SFF_NOWRFILES, flags) && bitset(S_IROTH, st->st_mode))
293         {
294                 if (tTd(44, 4))
295                         dprintf("\t[read bits %lo]\tE_SM_WRFILE\n",
296                                 (u_long) st->st_mode);
297                 return E_SM_WRFILE;
298         }
299         if (!bitset(SFF_EXECOK, flags) &&
300             bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) &&
301             bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode))
302         {
303                 if (tTd(44, 4))
304                         dprintf("\t[exec bits %lo]\tE_SM_ISEXEC]\n",
305                                 (u_long) st->st_mode);
306                 return E_SM_ISEXEC;
307         }
308         if (bitset(SFF_NOHLINK, flags) && st->st_nlink != 1)
309         {
310                 if (tTd(44, 4))
311                         dprintf("\t[link count %d]\tE_SM_NOHLINK\n",
312                                 (int) st->st_nlink);
313                 return E_SM_NOHLINK;
314         }
315
316         if (uid == 0 && bitset(SFF_OPENASROOT, flags))
317                 /* EMPTY */
318                 ;
319         else if (uid == 0 && !bitset(SFF_ROOTOK, flags))
320                 mode >>= 6;
321         else if (st->st_uid == uid)
322                 /* EMPTY */
323                 ;
324         else if (uid == 0 && st->st_uid == TrustedUid)
325                 /* EMPTY */
326                 ;
327         else
328         {
329                 mode >>= 3;
330                 if (st->st_gid == gid)
331                         /* EMPTY */
332                         ;
333 # ifndef NO_GROUP_SET
334                 else if (user != NULL && !DontInitGroups &&
335                          ((gr != NULL && gr->gr_gid == st->st_gid) ||
336                           (gr = getgrgid(st->st_gid)) != NULL))
337                 {
338                         register char **gp;
339
340                         for (gp = gr->gr_mem; *gp != NULL; gp++)
341                                 if (strcmp(*gp, user) == 0)
342                                         break;
343                         if (*gp == NULL)
344                                 mode >>= 3;
345                 }
346 # endif /* ! NO_GROUP_SET */
347                 else
348                         mode >>= 3;
349         }
350         if (tTd(44, 4))
351                 dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ",
352                         (int) st->st_uid, (int) st->st_nlink,
353                         (u_long) st->st_mode, (u_long) mode);
354         if ((st->st_uid == uid || st->st_uid == 0 ||
355              st->st_uid == TrustedUid ||
356              !bitset(SFF_MUSTOWN, flags)) &&
357             (st->st_mode & mode) == mode)
358         {
359                 if (tTd(44, 4))
360                         dprintf("\tOK\n");
361                 return 0;
362         }
363         if (tTd(44, 4))
364                 dprintf("\tEACCES\n");
365         return EACCES;
366 }
367 \f/*
368 **  SAFEDIRPATH -- check to make sure a path to a directory is safe
369 **
370 **      Safe means not writable and owned by the right folks.
371 **
372 **      Parameters:
373 **              fn -- filename to check.
374 **              uid -- user id to compare against.
375 **              gid -- group id to compare against.
376 **              user -- user name to compare against (used for group
377 **                      sets).
378 **              flags -- modifiers:
379 **                      SFF_ROOTOK -- ok to use root permissions to open.
380 **                      SFF_SAFEDIRPATH -- writable directories are considered
381 **                              to be fatal errors.
382 **              level -- symlink recursive level.
383 **              offset -- offset into fn to start checking from.
384 **
385 **      Returns:
386 **              0 -- if the directory path is "safe".
387 **              else -- an error number associated with the path.
388 */
389
390 int
391 safedirpath(fn, uid, gid, user, flags, level, offset)
392         char *fn;
393         UID_T uid;
394         GID_T gid;
395         char *user;
396         long flags;
397         int level;
398         int offset;
399 {
400         int ret = 0;
401         int mode = S_IWOTH;
402         char save = '\0';
403         char *saveptr = NULL;
404         char *p, *enddir;
405         register struct group *gr = NULL;
406         char s[MAXLINKPATHLEN + 1];
407         struct stat stbuf;
408
409         /* make sure we aren't in a symlink loop */
410         if (level > MAXSYMLINKS)
411                 return ELOOP;
412
413         /* special case root directory */
414         if (*fn == '\0')
415                 fn = "/";
416
417         if (tTd(44, 4))
418                 dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n",
419                         fn, (long) uid, (long) gid, flags, level, offset);
420
421         if (!bitnset(DBS_GROUPWRITABLEDIRPATHSAFE, DontBlameSendmail))
422                 mode |= S_IWGRP;
423
424         /* Make a modifiable copy of the filename */
425         if (strlcpy(s, fn, sizeof s) >= sizeof s)
426                 return EINVAL;
427
428         p = s + offset;
429         while (p != NULL)
430         {
431                 /* put back character */
432                 if (saveptr != NULL)
433                 {
434                         *saveptr = save;
435                         saveptr = NULL;
436                         p++;
437                 }
438
439                 if (*p == '\0')
440                         break;
441
442                 p = strchr(p, '/');
443
444                 /* Special case for root directory */
445                 if (p == s)
446                 {
447                         save = *(p + 1);
448                         saveptr = p + 1;
449                         *(p + 1) = '\0';
450                 }
451                 else if (p != NULL)
452                 {
453                         save = *p;
454                         saveptr = p;
455                         *p = '\0';
456                 }
457
458                 /* Heuristic: . and .. have already been checked */
459                 enddir = strrchr(s, '/');
460                 if (enddir != NULL &&
461                     (strcmp(enddir, "/..") == 0 ||
462                      strcmp(enddir, "/.") == 0))
463                         continue;
464
465                 if (tTd(44, 20))
466                         dprintf("\t[dir %s]\n", s);
467
468 # if HASLSTAT
469                 ret = lstat(s, &stbuf);
470 # else /* HASLSTAT */
471                 ret = stat(s, &stbuf);
472 # endif /* HASLSTAT */
473                 if (ret < 0)
474                 {
475                         ret = errno;
476                         break;
477                 }
478
479 # ifdef S_ISLNK
480                 /* Follow symlinks */
481                 if (S_ISLNK(stbuf.st_mode))
482                 {
483                         char *target;
484                         char buf[MAXPATHLEN + 1];
485
486                         memset(buf, '\0', sizeof buf);
487                         if (readlink(s, buf, sizeof buf) < 0)
488                         {
489                                 ret = errno;
490                                 break;
491                         }
492
493                         offset = 0;
494                         if (*buf == '/')
495                         {
496                                 target = buf;
497
498                                 /* If path is the same, avoid rechecks */
499                                 while (s[offset] == buf[offset] &&
500                                        s[offset] != '\0')
501                                         offset++;
502
503                                 if (s[offset] == '\0' && buf[offset] == '\0')
504                                 {
505                                         /* strings match, symlink loop */
506                                         return ELOOP;
507                                 }
508
509                                 /* back off from the mismatch */
510                                 if (offset > 0)
511                                         offset--;
512
513                                 /* Make sure we are at a directory break */
514                                 if (offset > 0 &&
515                                     s[offset] != '/' &&
516                                     s[offset] != '\0')
517                                 {
518                                         while (buf[offset] != '/' &&
519                                                offset > 0)
520                                                 offset--;
521                                 }
522                                 if (offset > 0 &&
523                                     s[offset] == '/' &&
524                                     buf[offset] == '/')
525                                 {
526                                         /* Include the trailing slash */
527                                         offset++;
528                                 }
529                         }
530                         else
531                         {
532                                 char *sptr;
533                                 char fullbuf[MAXLINKPATHLEN + 1];
534
535                                 sptr = strrchr(s, '/');
536                                 if (sptr != NULL)
537                                 {
538                                         *sptr = '\0';
539                                         offset = sptr + 1 - s;
540                                         if ((strlen(s) + 1 +
541                                              strlen(buf) + 1) > sizeof fullbuf)
542                                         {
543                                                 ret = EINVAL;
544                                                 break;
545                                         }
546                                         snprintf(fullbuf, sizeof fullbuf,
547                                                  "%s/%s", s, buf);
548                                         *sptr = '/';
549                                 }
550                                 else
551                                 {
552                                         if (strlen(buf) + 1 > sizeof fullbuf)
553                                         {
554                                                 ret = EINVAL;
555                                                 break;
556                                         }
557                                         (void) strlcpy(fullbuf, buf,
558                                                        sizeof fullbuf);
559                                 }
560                                 target = fullbuf;
561                         }
562                         ret = safedirpath(target, uid, gid, user, flags,
563                                           level + 1, offset);
564                         if (ret != 0)
565                                 break;
566
567                         /* Don't check permissions on the link file itself */
568                         continue;
569                 }
570 #endif /* S_ISLNK */
571
572                 if ((uid == 0 || bitset(SFF_SAFEDIRPATH, flags)) &&
573 #ifdef S_ISVTX
574                     !(bitnset(DBS_TRUSTSTICKYBIT, DontBlameSendmail) &&
575                       bitset(S_ISVTX, stbuf.st_mode)) &&
576 #endif /* S_ISVTX */
577                     bitset(mode, stbuf.st_mode))
578                 {
579                         if (tTd(44, 4))
580                                 dprintf("\t[dir %s] mode %lo ",
581                                         s, (u_long) stbuf.st_mode);
582                         if (bitset(SFF_SAFEDIRPATH, flags))
583                         {
584                                 if (bitset(S_IWOTH, stbuf.st_mode))
585                                         ret = E_SM_WWDIR;
586                                 else
587                                         ret = E_SM_GWDIR;
588                                 if (tTd(44, 4))
589                                         dprintf("FATAL\n");
590                                 break;
591                         }
592                         if (tTd(44, 4))
593                                 dprintf("WARNING\n");
594                         if (Verbose > 1)
595                                 message("051 WARNING: %s writable directory %s",
596                                         bitset(S_IWOTH, stbuf.st_mode)
597                                            ? "World"
598                                            : "Group",
599                                         s);
600                 }
601                 if (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags))
602                 {
603                         if (bitset(S_IXOTH, stbuf.st_mode))
604                                 continue;
605                         ret = EACCES;
606                         break;
607                 }
608
609                 /*
610                 **  Let OS determine access to file if we are not
611                 **  running as a privileged user.  This allows ACLs
612                 **  to work.  Also, if opening as root, assume we can
613                 **  scan the directory.
614                 */
615                 if (geteuid() != 0 || bitset(SFF_OPENASROOT, flags))
616                         continue;
617
618                 if (stbuf.st_uid == uid &&
619                     bitset(S_IXUSR, stbuf.st_mode))
620                         continue;
621                 if (stbuf.st_gid == gid &&
622                     bitset(S_IXGRP, stbuf.st_mode))
623                         continue;
624 # ifndef NO_GROUP_SET
625                 if (user != NULL && !DontInitGroups &&
626                     ((gr != NULL && gr->gr_gid == stbuf.st_gid) ||
627                      (gr = getgrgid(stbuf.st_gid)) != NULL))
628                 {
629                         register char **gp;
630
631                         for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++)
632                                 if (strcmp(*gp, user) == 0)
633                                         break;
634                         if (gp != NULL && *gp != NULL &&
635                             bitset(S_IXGRP, stbuf.st_mode))
636                                 continue;
637                 }
638 # endif /* ! NO_GROUP_SET */
639                 if (!bitset(S_IXOTH, stbuf.st_mode))
640                 {
641                         ret = EACCES;
642                         break;
643                 }
644         }
645         if (tTd(44, 4))
646                 dprintf("\t[dir %s] %s\n", fn,
647                         ret == 0 ? "OK" : errstring(ret));
648         return ret;
649 }
650 \f/*
651 **  SAFEOPEN -- do a file open with extra checking
652 **
653 **      Parameters:
654 **              fn -- the file name to open.
655 **              omode -- the open-style mode flags.
656 **              cmode -- the create-style mode flags.
657 **              sff -- safefile flags.
658 **
659 **      Returns:
660 **              Same as open.
661 */
662
663 #ifndef O_ACCMODE
664 # define O_ACCMODE      (O_RDONLY|O_WRONLY|O_RDWR)
665 #endif /* ! O_ACCMODE */
666
667 int
668 safeopen(fn, omode, cmode, sff)
669         char *fn;
670         int omode;
671         int cmode;
672         long sff;
673 {
674         int rval;
675         int fd;
676         int smode;
677         struct stat stb;
678
679         if (tTd(44, 10))
680                 printf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n",
681                        fn, omode, cmode, sff);
682
683         if (bitset(O_CREAT, omode))
684                 sff |= SFF_CREAT;
685         omode &= ~O_CREAT;
686         smode = 0;
687         switch (omode & O_ACCMODE)
688         {
689           case O_RDONLY:
690                 smode = S_IREAD;
691                 break;
692
693           case O_WRONLY:
694                 smode = S_IWRITE;
695                 break;
696
697           case O_RDWR:
698                 smode = S_IREAD|S_IWRITE;
699                 break;
700
701           default:
702                 smode = 0;
703                 break;
704         }
705         if (bitset(SFF_OPENASROOT, sff))
706                 rval = safefile(fn, RunAsUid, RunAsGid, RunAsUserName,
707                                 sff, smode, &stb);
708         else
709                 rval = safefile(fn, RealUid, RealGid, RealUserName,
710                                 sff, smode, &stb);
711         if (rval != 0)
712         {
713                 errno = rval;
714                 return -1;
715         }
716         if (stb.st_mode == ST_MODE_NOFILE && bitset(SFF_CREAT, sff))
717                 omode |= O_CREAT | (bitset(SFF_NOTEXCL, sff) ? 0 : O_EXCL);
718         else if (bitset(SFF_CREAT, sff) && bitset(O_EXCL, omode))
719         {
720                 /* The file exists so an exclusive create would fail */
721                 errno = EEXIST;
722                 return -1;
723         }
724
725         fd = dfopen(fn, omode, cmode, sff);
726         if (fd < 0)
727                 return fd;
728         if (filechanged(fn, fd, &stb))
729         {
730                 syserr("554 5.3.0 cannot open: file %s changed after open", fn);
731                 (void) close(fd);
732                 errno = E_SM_FILECHANGE;
733                 return -1;
734         }
735         return fd;
736 }
737 \f/*
738 **  SAFEFOPEN -- do a file open with extra checking
739 **
740 **      Parameters:
741 **              fn -- the file name to open.
742 **              omode -- the open-style mode flags.
743 **              cmode -- the create-style mode flags.
744 **              sff -- safefile flags.
745 **
746 **      Returns:
747 **              Same as fopen.
748 */
749
750 FILE *
751 safefopen(fn, omode, cmode, sff)
752         char *fn;
753         int omode;
754         int cmode;
755         long sff;
756 {
757         int fd;
758         int save_errno;
759         FILE *fp;
760         char *fmode;
761
762         switch (omode & O_ACCMODE)
763         {
764           case O_RDONLY:
765                 fmode = "r";
766                 break;
767
768           case O_WRONLY:
769                 if (bitset(O_APPEND, omode))
770                         fmode = "a";
771                 else
772                         fmode = "w";
773                 break;
774
775           case O_RDWR:
776                 if (bitset(O_TRUNC, omode))
777                         fmode = "w+";
778                 else if (bitset(O_APPEND, omode))
779                         fmode = "a+";
780                 else
781                         fmode = "r+";
782                 break;
783
784           default:
785                 syserr("554 5.3.5 safefopen: unknown omode %o", omode);
786                 fmode = "x";
787         }
788         fd = safeopen(fn, omode, cmode, sff);
789         if (fd < 0)
790         {
791                 save_errno = errno;
792                 if (tTd(44, 10))
793                         dprintf("safefopen: safeopen failed: %s\n",
794                                 errstring(errno));
795                 errno = save_errno;
796                 return NULL;
797         }
798         fp = fdopen(fd, fmode);
799         if (fp != NULL)
800                 return fp;
801
802         save_errno = errno;
803         if (tTd(44, 10))
804         {
805                 dprintf("safefopen: fdopen(%s, %s) failed: omode=%x, sff=%lx, err=%s\n",
806                         fn, fmode, omode, sff, errstring(errno));
807         }
808         (void) close(fd);
809         errno = save_errno;
810         return NULL;
811 }
812 \f/*
813 **  FILECHANGED -- check to see if file changed after being opened
814 **
815 **      Parameters:
816 **              fn -- pathname of file to check.
817 **              fd -- file descriptor to check.
818 **              stb -- stat structure from before open.
819 **
820 **      Returns:
821 **              TRUE -- if a problem was detected.
822 **              FALSE -- if this file is still the same.
823 */
824
825 bool
826 filechanged(fn, fd, stb)
827         char *fn;
828         int fd;
829         struct stat *stb;
830 {
831         struct stat sta;
832
833         if (stb->st_mode == ST_MODE_NOFILE)
834         {
835 # if HASLSTAT && BOGUS_O_EXCL
836                 /* only necessary if exclusive open follows symbolic links */
837                 if (lstat(fn, stb) < 0 || stb->st_nlink != 1)
838                         return TRUE;
839 # else /* HASLSTAT && BOGUS_O_EXCL */
840                 return FALSE;
841 # endif /* HASLSTAT && BOGUS_O_EXCL */
842         }
843         if (fstat(fd, &sta) < 0)
844                 return TRUE;
845
846         if (sta.st_nlink != stb->st_nlink ||
847             sta.st_dev != stb->st_dev ||
848             sta.st_ino != stb->st_ino ||
849 # if HAS_ST_GEN && 0            /* AFS returns garbage in st_gen */
850             sta.st_gen != stb->st_gen ||
851 # endif /* HAS_ST_GEN && 0 */
852             sta.st_uid != stb->st_uid ||
853             sta.st_gid != stb->st_gid)
854         {
855                 if (tTd(44, 8))
856                 {
857                         dprintf("File changed after opening:\n");
858                         dprintf(" nlink = %ld/%ld\n",
859                                 (long) stb->st_nlink, (long) sta.st_nlink);
860                         dprintf(" dev   = %ld/%ld\n",
861                                 (long) stb->st_dev, (long) sta.st_dev);
862                         if (sizeof sta.st_ino > sizeof (long))
863                         {
864                                 dprintf(" ino   = %s/",
865                                         quad_to_string(stb->st_ino));
866                                 dprintf("%s\n",
867                                         quad_to_string(sta.st_ino));
868                         }
869                         else
870                                 dprintf(" ino   = %lu/%lu\n",
871                                         (unsigned long) stb->st_ino,
872                                         (unsigned long) sta.st_ino);
873 # if HAS_ST_GEN
874                         dprintf(" gen   = %ld/%ld\n",
875                                 (long) stb->st_gen, (long) sta.st_gen);
876 # endif /* HAS_ST_GEN */
877                         dprintf(" uid   = %ld/%ld\n",
878                                 (long) stb->st_uid, (long) sta.st_uid);
879                         dprintf(" gid   = %ld/%ld\n",
880                                 (long) stb->st_gid, (long) sta.st_gid);
881                 }
882                 return TRUE;
883         }
884
885         return FALSE;
886 }
887 \f/*
888 **  DFOPEN -- determined file open
889 **
890 **      This routine has the semantics of open, except that it will
891 **      keep trying a few times to make this happen.  The idea is that
892 **      on very loaded systems, we may run out of resources (inodes,
893 **      whatever), so this tries to get around it.
894 */
895
896 int
897 dfopen(filename, omode, cmode, sff)
898         char *filename;
899         int omode;
900         int cmode;
901         long sff;
902 {
903         register int tries;
904         int fd = -1;
905         struct stat st;
906
907         for (tries = 0; tries < 10; tries++)
908         {
909                 (void) sleep((unsigned) (10 * tries));
910                 errno = 0;
911                 fd = open(filename, omode, cmode);
912                 if (fd >= 0)
913                         break;
914                 switch (errno)
915                 {
916                   case ENFILE:          /* system file table full */
917                   case EINTR:           /* interrupted syscall */
918 #ifdef ETXTBSY
919                   case ETXTBSY:         /* Apollo: net file locked */
920 #endif /* ETXTBSY */
921                         continue;
922                 }
923                 break;
924         }
925         if (!bitset(SFF_NOLOCK, sff) &&
926             fd >= 0 &&
927             fstat(fd, &st) >= 0 &&
928             S_ISREG(st.st_mode))
929         {
930                 int locktype;
931
932                 /* lock the file to avoid accidental conflicts */
933                 if ((omode & O_ACCMODE) != O_RDONLY)
934                         locktype = LOCK_EX;
935                 else
936                         locktype = LOCK_SH;
937                 if (!lockfile(fd, filename, NULL, locktype))
938                 {
939                         int save_errno = errno;
940
941                         (void) close(fd);
942                         fd = -1;
943                         errno = save_errno;
944                 }
945                 else
946                         errno = 0;
947         }
948         return fd;
949 }