/* * fopen_as_user() - open a file using uid:gid privileges only. * * This is the magic, forking, file-descriptor passing, version! * A user-land work-around for systems missing a true fopen_as_user(3). * * This function can be safely used by privileged processes to access a file * using only the privileges of a specified uid:gid, including when the * privileged process may not have access due to restrictions that might be * imposed on a network client's superuser by a remote file server. * * It works on *BSD socket systems that can pass file descriptors with * sendmsg() & recvmsg(). * * It currently also has hooks into *BSD stdio internals, specifically the * __sflags() function which parses an fopen()/fdopen()/freopen() mode string * and turns it into flags for open(2). * * It also does not yet use initgroups() under the assumption that it's not * going to be the group ownership of a file which is critical to gaining its * access. */ #ident "@(#):fopen_as_user.c 1.3 03/06/14 23:37:08 ()" /* * This compilation Copyright (c) by Greg A. Woods * * Years of publication: 2003 * * Redistribution of this software in both source and binary forms, with * or without modification, is permitted provided that all of the * following conditions are met: * * 1. Redistributions of source code, either alone or as part of a * collective work, must retain this entire copyright notice, and the * following disclaimer, without alteration, in each file that * contains part of this software. * * 2. Redistributions of this software in binary form, either alone or * as part of a collective work, must reproduce this entire copyright * notice, and the following disclaimer, without alteration, in * either the documentation (as text files in electronic media, or in * printed matter), and/or any original header files from this * software as per the previous term, and/or other materials provided * as part of the distribution. * * 3. Collective works including this software must also include the * following acknowledgement, either alone or as part of this entire * copyright license, in any printed documentation accompanying a * physical distribution (if there is printed documentation), and in * a plain text file separate from the archive files (but perhaps * along with other similar acknowledgments) on any electronic * medium: * * This product includes software developed by Greg A. Woods. * * 4. The name of the author may NOT be used to endorse or promote * products derived from this software without specific prior written * permission. The use of the author's name strictly to meet the * requirements of the previous terms is not to be considered * promotion or endorsement under this term. * * 5. Altered versions (derivative works) must be plainly marked as * such, and must not be misrepresented as being the original * software. This copyright notice, and the following disclaimer, * must not be removed from any derivative work and must not be * changed in any way. * * All other rights are reserved. * * DISCLAIMER: * * THIS SOFTWARE IS PROVIDED BY GREG A. WOODS ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include FILE *fopen_as_user __P((char *, char *, uid_t, gid_t)); #ifndef HAVE_FOPEN_AS_USER static ssize_t read_fd __P((int, void *, size_t, int *)); /* * file descriptor passing helper function * * More or less copied from W. Richard Stevens' "UNIX Network Programming", * Vol. 1 (2nd ed), p. 387 (obsolete code for old recvmsg() deleted, and * converted to use malloc() because CMSG_SPACE() is not a compile-time * constant on NetBSD) */ static ssize_t read_fd(fd, ptr, nbytes, precvfd) int fd; /* AF_LOCAL socket */ void *ptr; /* for ancilliary data */ size_t nbytes; /* size of ptr storage */ int *precvfd; /* pointer for storing descriptor */ { struct msghdr msg; struct iovec iov; ssize_t n; char *control; struct cmsghdr *cmptr; int oerrno; *precvfd = -1; /* assume the worst.... */ if (!(control = malloc(CMSG_SPACE(sizeof(*precvfd))))) return -1; memset(&msg, 0, sizeof(msg)); msg.msg_control = control; msg.msg_controllen = (socklen_t) CMSG_SPACE(sizeof(*precvfd)); /* CMSG_LEN()??? */ msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_flags = 0; iov.iov_base = ptr; iov.iov_len = nbytes; msg.msg_iov = &iov; msg.msg_iovlen = 1; n = recvmsg(fd, &msg, 0); oerrno = errno; if (n <= 0) { free(control); errno = oerrno; return -1; } cmptr = CMSG_FIRSTHDR(&msg); if (!cmptr || cmptr->cmsg_len != CMSG_LEN(sizeof(*precvfd))) { free(control); errno = EBADF; /* descriptor was not passed */ return -1; } if (cmptr->cmsg_level != SOL_SOCKET) { free(control); errno = EBADF; return -1; } if (cmptr->cmsg_type != SCM_RIGHTS) { free(control); errno = EBADF; return -1; } *precvfd = *((int *) ((void *) CMSG_DATA(cmptr))); free(control); errno = oerrno; return (n); } static ssize_t write_fd __P((int, void *, size_t, int)); /* * this declaration borrowed from: * * src/lib/libc/stdio/local.h */ extern int __sflags __P((const char *, int *)); /* * file descriptor passing helper function * * More or less copied from W. Richard Stevens' "UNIX Network Programming", * Vol. 1 (2nd ed), p. 389 (obsolete code for old recvmsg() deleted, and * converted to use malloc() because CMSG_SPACE() is not a compile-time * constant on NetBSD) */ static ssize_t write_fd(fd, ptr, nbytes, sendfd) int fd; /* AF_LOCAL socket */ void *ptr; /* for ancilliary data */ size_t nbytes; /* size of ancilliary data */ int sendfd; /* descriptor to send */ { struct msghdr msg; struct iovec iov; char *control; struct cmsghdr *cmptr; int oerrno; int rv; if (!(control = malloc(CMSG_SPACE(sizeof(sendfd))))) return -1; memset(&msg, 0, sizeof(msg)); msg.msg_control = control; msg.msg_controllen = (socklen_t) CMSG_LEN(sizeof(sendfd)); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_flags = 0; cmptr = CMSG_FIRSTHDR(&msg); cmptr->cmsg_len = CMSG_LEN(sizeof(sendfd)); cmptr->cmsg_level = SOL_SOCKET; cmptr->cmsg_type = SCM_RIGHTS; *((int *) ((void *) CMSG_DATA(cmptr))) = sendfd; iov.iov_base = ptr; iov.iov_len = nbytes; msg.msg_iov = &iov; msg.msg_iovlen = 1; rv = sendmsg(fd, &msg, 0); oerrno = errno; free(control); errno = oerrno; return rv; } /* * fopen_as_user() - open a file using uid:gid privileges only. * * More or less copied from W. Richard Stevens' "UNIX Network Programming", * Vol. 1 (2nd ed), p. 385 * * XXX WARNING: This is an incomplete implementation! (e.g. no initgroups()) */ FILE * fopen_as_user(path, mode, uid, gid) char *path; char *mode; uid_t uid; gid_t gid; { int fd; int pid; int sockfd[2]; int status; FILE *fp = NULL; if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd) < 0) return NULL; switch ((pid = fork())) { case -1: { int oerrno = errno; /* fork() error */ close(sockfd[0]); close(sockfd[1]); errno = oerrno; return NULL; /* NOTREACHED */ } case 0: { int cfd; int oflags; # if (ELAST > 254) # include "ERORR: This code returns errno via _exit() and ELAST > 254!" # endif /* in the child process */ /* * Take care to use _exit() so as to avoid any side-effects of * exit(), such as atexit()s registered by the parent process. * * Note this means we have to be very careful to always close * what we open! */ close(sockfd[0]); if (setgid(gid) != 0) { int oerrno = errno; close(sockfd[1]); _exit(oerrno + 1); } if (setuid(uid) != 0) { int oerrno = errno; close(sockfd[1]); _exit(oerrno + 1); } if (__sflags(mode, &oflags) == 0) { /* magic *BSD stdio internals */ int oerrno = errno; close(sockfd[1]); _exit(oerrno + 1); } if ((cfd = open(path, oflags, DEFFILEMODE)) < 0) { int oerrno = errno; close(sockfd[1]); _exit(oerrno + 1); } /* * When sending a descriptor across a stream pipe we always * send at least one byte of datea, even if the receiver does * nothing with the data, otherwise the receiver cannot tell * whether a return value of 0 from read_fd() means "no data * (but possibly a descriptor)", or just "end of file". */ if (write_fd(sockfd[1], "", sizeof(""), cfd) < 0) { int oerrno = errno; close(sockfd[1]); close(cfd); _exit(oerrno + 1); } close(cfd); close(sockfd[1]); _exit(0); /* NOTREACHED */ } default: /* in the parent process */ close(sockfd[1]); if (waitpid(pid, &status, 0) < 0) { /* WUNTRACED??? */ int oerrno = errno; close(sockfd[0]); errno = oerrno; return NULL; } if (WIFEXITED(status)) { char ch; if (WEXITSTATUS(status)) { close(sockfd[0]); errno = WEXITSTATUS(status) - 1; return NULL; } if (read_fd(sockfd[0], &ch, 1, &fd) <= 0) { int oerrno = errno; close(sockfd[0]); errno = oerrno; return NULL; } if (fd < 0) { int oerrno = errno; close(sockfd[0]); errno = oerrno; return NULL; } close(sockfd[0]); } else { close(sockfd[0]); errno = EINTR; return NULL; } } if (!(fp = fdopen(fd, mode))) { int oerrno = errno; close(fd); errno = oerrno; return NULL; } return fp; } #endif /* HAVE_FOPEN_AS_USER */