1/* 2 Unix SMB/CIFS implementation. 3 run a command as a specified user 4 Copyright (C) Andrew Tridgell 1992-1998 5 6 This program is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program; if not, write to the Free Software 18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 20 modified for netatalk dgautheron@magic.fr 21*/ 22 23#ifdef HAVE_CONFIG_H 24#include "config.h" 25#endif /* HAVE_CONFIG_H */ 26 27#include <stdio.h> 28#include <stdlib.h> 29 30#include <sys/types.h> 31#define __USE_GNU 1 32#include <unistd.h> 33 34#include <errno.h> 35 36#ifdef HAVE_SYS_WAIT_H 37#include <sys/wait.h> 38#endif 39 40#include <sys/param.h> 41#include <string.h> 42 43/* FIXME */ 44#ifdef linux 45#ifndef USE_SETRESUID 46#define USE_SETRESUID 1 47#endif 48#else 49#ifndef USE_SETEUID 50#define USE_SETEUID 1 51#endif 52#endif 53 54#include <atalk/logger.h> 55 56/**************************************************************************n 57 Find a suitable temporary directory. The result should be copied immediately 58 as it may be overwritten by a subsequent call. 59 ****************************************************************************/ 60 61static const char *tmpdir(void) 62{ 63 char *p; 64 65 if ((p = getenv("TMPDIR"))) 66 return p; 67 return "/tmp"; 68} 69 70/**************************************************************************** 71This is a utility function of afprun(). 72****************************************************************************/ 73 74static int setup_out_fd(void) 75{ 76 int fd; 77 char path[MAXPATHLEN +1]; 78 79 snprintf(path, sizeof(path)-1, "%s/afp.XXXXXX", tmpdir()); 80 81 /* now create the file */ 82 fd = mkstemp(path); 83 84 if (fd == -1) { 85 LOG(log_error, logtype_afpd, "setup_out_fd: Failed to create file %s. (%s)",path, strerror(errno) ); 86 return -1; 87 } 88 89 /* Ensure file only kept around by open fd. */ 90 unlink(path); 91 return fd; 92} 93 94/**************************************************************************** 95 Gain root privilege before doing something. 96 We want to end up with ruid==euid==0 97****************************************************************************/ 98static void gain_root_privilege(void) 99{ 100 seteuid(0); 101} 102 103/**************************************************************************** 104 Ensure our real and effective groups are zero. 105 we want to end up with rgid==egid==0 106****************************************************************************/ 107static void gain_root_group_privilege(void) 108{ 109 setegid(0); 110} 111 112/**************************************************************************** 113 Become the specified uid and gid - permanently ! 114 there should be no way back if possible 115****************************************************************************/ 116static void become_user_permanently(uid_t uid, gid_t gid) 117{ 118 /* 119 * First - gain root privilege. We do this to ensure 120 * we can lose it again. 121 */ 122 123 gain_root_privilege(); 124 gain_root_group_privilege(); 125 126#if USE_SETRESUID 127 setresgid(gid,gid,gid); 128 setgid(gid); 129 setresuid(uid,uid,uid); 130 setuid(uid); 131#endif 132 133#if USE_SETREUID 134 setregid(gid,gid); 135 setgid(gid); 136 setreuid(uid,uid); 137 setuid(uid); 138#endif 139 140#if USE_SETEUID 141 setegid(gid); 142 setgid(gid); 143 setuid(uid); 144 seteuid(uid); 145 setuid(uid); 146#endif 147 148#if USE_SETUIDX 149 setgidx(ID_REAL, gid); 150 setgidx(ID_EFFECTIVE, gid); 151 setgid(gid); 152 setuidx(ID_REAL, uid); 153 setuidx(ID_EFFECTIVE, uid); 154 setuid(uid); 155#endif 156} 157 158/**************************************************************************** 159run a command being careful about uid/gid handling and putting the output in 160outfd (or discard it if outfd is NULL). 161****************************************************************************/ 162 163int afprun(int root, char *cmd, int *outfd) 164{ 165 pid_t pid; 166 uid_t uid = geteuid(); 167 gid_t gid = getegid(); 168 169 /* point our stdout at the file we want output to go into */ 170 if (outfd && ((*outfd = setup_out_fd()) == -1)) { 171 return -1; 172 } 173 LOG(log_debug, logtype_afpd, "running %s as user %d", cmd, root?0:uid); 174 /* in this method we will exec /bin/sh with the correct 175 arguments, after first setting stdout to point at the file */ 176 177 if ((pid=fork()) < 0) { 178 LOG(log_error, logtype_afpd, "afprun: fork failed with error %s", strerror(errno) ); 179 if (outfd) { 180 close(*outfd); 181 *outfd = -1; 182 } 183 return errno; 184 } 185 186 if (pid) { 187 /* 188 * Parent. 189 */ 190 int status=0; 191 pid_t wpid; 192 193 /* the parent just waits for the child to exit */ 194 while((wpid = waitpid(pid,&status,0)) < 0) { 195 if (errno == EINTR) { 196 errno = 0; 197 continue; 198 } 199 break; 200 } 201 if (wpid != pid) { 202 LOG(log_error, logtype_afpd, "waitpid(%d) : %s",(int)pid, strerror(errno) ); 203 if (outfd) { 204 close(*outfd); 205 *outfd = -1; 206 } 207 return -1; 208 } 209 /* Reset the seek pointer. */ 210 if (outfd) { 211 lseek(*outfd, 0, SEEK_SET); 212 } 213 214#if defined(WIFEXITED) && defined(WEXITSTATUS) 215 if (WIFEXITED(status)) { 216 return WEXITSTATUS(status); 217 } 218#endif 219 return status; 220 } 221 222 /* we are in the child. we exec /bin/sh to do the work for us. we 223 don't directly exec the command we want because it may be a 224 pipeline or anything else the config file specifies */ 225 226 /* point our stdout at the file we want output to go into */ 227 if (outfd) { 228 close(1); 229 if (dup2(*outfd,1) != 1) { 230 LOG(log_error, logtype_afpd, "Failed to create stdout file descriptor"); 231 close(*outfd); 232 exit(80); 233 } 234 } 235 236 if (chdir("/") < 0) { 237 LOG(log_error, logtype_afpd, "afprun: can't change directory to \"/\" %s", strerror(errno) ); 238 exit(83); 239 } 240 241 /* now completely lose our privileges. This is a fairly paranoid 242 way of doing it, but it does work on all systems that I know of */ 243 if (root) { 244 become_user_permanently(0, 0); 245 uid = gid = 0; 246 } 247 else { 248 become_user_permanently(uid, gid); 249 } 250 if (getuid() != uid || geteuid() != uid || getgid() != gid || getegid() != gid) { 251 /* we failed to lose our privileges - do not execute the command */ 252 exit(81); /* we can't print stuff at this stage, instead use exit codes for debugging */ 253 } 254 255 /* close all other file descriptors, leaving only 0, 1 and 2. 0 and 256 2 point to /dev/null from the startup code */ 257 { 258 int fd; 259 for (fd=3;fd<256;fd++) close(fd); 260 } 261 262 execl("/bin/sh","sh","-c",cmd,NULL); 263 /* not reached */ 264 exit(82); 265 return 1; 266} 267