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