/* * Safe way to create/open a file in a sticky directory. * the file must belong to the executing user. * * Casper Dik (casper@fwi.uva.nl) */ #include #include #include #include #include #include #include #include #include #ifndef FILEMODE #define FILEMODE 0600 #endif #ifndef FILEGROUP #define FILEGROUP -1 #endif #define FIXMODE FILE * openmbox(uid_t, char *); main(int argc, char **argv) { struct passwd *pwd; FILE *mbox; int c; if (argc != 3) { fprintf(stderr,"Usage: %s file user\n", argv[0]); exit(1); } pwd = getpwnam(argv[2]); if (pwd == 0) { fprintf(stderr,"%s: %s: unknown user\n", argv[0], argv[1]); exit(1); } if ((mbox = openmbox(pwd->pw_uid, argv[1])) == 0) { fprintf(stderr,"Couldn't safely open %s\n", argv[1]); exit(1); } while((c = getchar()) != EOF) putc(c, mbox); exit(0); } /* * A procedure to safely open and create a mailbox in a worl-writable * sticky directory. * It should have no race conditions and should not allow * overwriting/creating random files for the user. * Essential parts are: * - openening with open(2), not fopen(3S), for maximum control * - splitting the open of an existing file and the create of a new * mailbox. * - combining the open with the validity checks */ FILE * openmbox(uid_t uid, char *file) { int fd; FILE *f; /* First we'll try a normal open, no O_CREAT */ fd = open(file, O_WRONLY|O_APPEND); if (fd == -1) { if (errno != ENOENT) { /* If there's another error, we can't handle it */ perror(file); return 0; } /* Create the file. It must not exist. If it does exist, fail. */ fd = open(file, O_WRONLY|O_APPEND|O_CREAT|O_EXCL, FILEMODE); if (fd < 0) { fprintf(stderr,"Can't create %s\n", file); return 0; } /* * At this point we have the file and an open filedescriptor * We can be sure we aren't overwriting another file because we've * created this file in the spool directory. * Only on systems where O_CREAT|O_EXCL creates files at the end * of symlinks we have to worry */ } else { struct stat fdbuf, filebuf; /* the next two stats should never fail */ if (fstat(fd, &fdbuf) == -1) { perror("fstat"); return 0; } if (lstat(file, &filebuf) == -1) { perror(file); return 0; } /* Now check that: file and fd reference the same file, file only has one link, file is plain file */ if (fdbuf.st_dev != filebuf.st_dev || fdbuf.st_ino != filebuf.st_ino || fdbuf.st_nlink != 1 || filebuf.st_nlink != 1 || (fdbuf.st_mode & S_IFMT) != S_IFREG) { fprintf(stderr,"%s must be a plain file with one link\n", file); return 0; } /* we have a filedescriptor pointing to a file in the spool directory * how can we be sure it wasn't pointing at another file when we * opened it? * At the time of the lstat, the file in the spool directory was * a hardlink to the file we previously opened. * (st_dev and st_ino uniquely identify a file) * It was the only hardlink. * Could it have been a different file previously? * Yes, but only if the file existed in a writable directory and * no longer exists there now. */ } /* Now we have an fd pointing to the file in the spool directory. * Now we're going to fix ownership and mode */ #ifdef FIXMODE (void) fchmod(fd, FILEMODE); (void) fchown(fd, uid, FILEGROUP); #endif f = fdopen(fd, "a"); if (f == 0) close(fd); return f; }