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 21#include "includes.h" 22 23/* need to move this from here!! need some sleep ... */ 24struct current_user current_user; 25 26/**************************************************************************** 27This is a utility function of smbrun(). 28****************************************************************************/ 29 30static int setup_out_fd(void) 31{ 32 int fd; 33 pstring path; 34 35 slprintf(path, sizeof(path)-1, "%s/smb.XXXXXX", tmpdir()); 36 37 /* now create the file */ 38 fd = smb_mkstemp(path); 39 40 if (fd == -1) { 41 DEBUG(0,("setup_out_fd: Failed to create file %s. (%s)\n", 42 path, strerror(errno) )); 43 return -1; 44 } 45 46 DEBUG(10,("setup_out_fd: Created tmp file %s\n", path )); 47 48 /* Ensure file only kept around by open fd. */ 49 unlink(path); 50 return fd; 51} 52 53/**************************************************************************** 54run a command being careful about uid/gid handling and putting the output in 55outfd (or discard it if outfd is NULL). 56****************************************************************************/ 57 58static int smbrun_internal(const char *cmd, int *outfd, BOOL sanitize) 59{ 60 pid_t pid; 61 uid_t uid = current_user.ut.uid; 62 gid_t gid = current_user.ut.gid; 63 64 /* 65 * Lose any elevated privileges. 66 */ 67 drop_effective_capability(KERNEL_OPLOCK_CAPABILITY); 68 drop_effective_capability(DMAPI_ACCESS_CAPABILITY); 69 70 /* point our stdout at the file we want output to go into */ 71 72 if (outfd && ((*outfd = setup_out_fd()) == -1)) { 73 return -1; 74 } 75 76 /* in this method we will exec /bin/sh with the correct 77 arguments, after first setting stdout to point at the file */ 78 79 /* 80 * We need to temporarily stop CatchChild from eating 81 * SIGCLD signals as it also eats the exit status code. JRA. 82 */ 83 84 CatchChildLeaveStatus(); 85 86 if ((pid=sys_fork()) < 0) { 87 DEBUG(0,("smbrun: fork failed with error %s\n", strerror(errno) )); 88 CatchChild(); 89 if (outfd) { 90 close(*outfd); 91 *outfd = -1; 92 } 93 return errno; 94 } 95 96 if (pid) { 97 /* 98 * Parent. 99 */ 100 int status=0; 101 pid_t wpid; 102 103 104 /* the parent just waits for the child to exit */ 105 while((wpid = sys_waitpid(pid,&status,0)) < 0) { 106 if(errno == EINTR) { 107 errno = 0; 108 continue; 109 } 110 break; 111 } 112 113 CatchChild(); 114 115 if (wpid != pid) { 116 DEBUG(2,("waitpid(%d) : %s\n",(int)pid,strerror(errno))); 117 if (outfd) { 118 close(*outfd); 119 *outfd = -1; 120 } 121 return -1; 122 } 123 124 /* Reset the seek pointer. */ 125 if (outfd) { 126 sys_lseek(*outfd, 0, SEEK_SET); 127 } 128 129#if defined(WIFEXITED) && defined(WEXITSTATUS) 130 if (WIFEXITED(status)) { 131 return WEXITSTATUS(status); 132 } 133#endif 134 135 return status; 136 } 137 138 CatchChild(); 139 140 /* we are in the child. we exec /bin/sh to do the work for us. we 141 don't directly exec the command we want because it may be a 142 pipeline or anything else the config file specifies */ 143 144 /* point our stdout at the file we want output to go into */ 145 if (outfd) { 146 close(1); 147 if (sys_dup2(*outfd,1) != 1) { 148 DEBUG(2,("Failed to create stdout file descriptor\n")); 149 close(*outfd); 150 exit(80); 151 } 152 } 153 154 /* now completely lose our privileges. This is a fairly paranoid 155 way of doing it, but it does work on all systems that I know of */ 156 157 become_user_permanently(uid, gid); 158 159 if (getuid() != uid || geteuid() != uid || 160 getgid() != gid || getegid() != gid) { 161 /* we failed to lose our privileges - do not execute 162 the command */ 163 exit(81); /* we can't print stuff at this stage, 164 instead use exit codes for debugging */ 165 } 166 167#ifndef __INSURE__ 168 /* close all other file descriptors, leaving only 0, 1 and 2. 0 and 169 2 point to /dev/null from the startup code */ 170 { 171 int fd; 172 for (fd=3;fd<256;fd++) close(fd); 173 } 174#endif 175 176 { 177 const char *newcmd = sanitize ? escape_shell_string(cmd) : cmd; 178 if (!newcmd) { 179 exit(82); 180 } 181 execl("/bin/sh","sh","-c",newcmd,NULL); 182 } 183 184 /* not reached */ 185 exit(83); 186 return 1; 187} 188 189/**************************************************************************** 190 Use only in known safe shell calls (printing). 191****************************************************************************/ 192 193int smbrun_no_sanitize(const char *cmd, int *outfd) 194{ 195 return smbrun_internal(cmd, outfd, False); 196} 197 198/**************************************************************************** 199 By default this now sanitizes shell expansion. 200****************************************************************************/ 201 202int smbrun(const char *cmd, int *outfd) 203{ 204 return smbrun_internal(cmd, outfd, True); 205} 206 207/**************************************************************************** 208run a command being careful about uid/gid handling and putting the output in 209outfd (or discard it if outfd is NULL). 210sends the provided secret to the child stdin. 211****************************************************************************/ 212 213int smbrunsecret(const char *cmd, const char *secret) 214{ 215 pid_t pid; 216 uid_t uid = current_user.ut.uid; 217 gid_t gid = current_user.ut.gid; 218 int ifd[2]; 219 220 /* 221 * Lose any elevated privileges. 222 */ 223 drop_effective_capability(KERNEL_OPLOCK_CAPABILITY); 224 drop_effective_capability(DMAPI_ACCESS_CAPABILITY); 225 226 /* build up an input pipe */ 227 if(pipe(ifd)) { 228 return -1; 229 } 230 231 /* in this method we will exec /bin/sh with the correct 232 arguments, after first setting stdout to point at the file */ 233 234 /* 235 * We need to temporarily stop CatchChild from eating 236 * SIGCLD signals as it also eats the exit status code. JRA. 237 */ 238 239 CatchChildLeaveStatus(); 240 241 if ((pid=sys_fork()) < 0) { 242 DEBUG(0, ("smbrunsecret: fork failed with error %s\n", strerror(errno))); 243 CatchChild(); 244 return errno; 245 } 246 247 if (pid) { 248 /* 249 * Parent. 250 */ 251 int status = 0; 252 pid_t wpid; 253 size_t towrite; 254 ssize_t wrote; 255 256 close(ifd[0]); 257 /* send the secret */ 258 towrite = strlen(secret); 259 wrote = write(ifd[1], secret, towrite); 260 if ( wrote != towrite ) { 261 DEBUG(0,("smbrunsecret: wrote %ld of %lu bytes\n",(long)wrote,(unsigned long)towrite)); 262 } 263 fsync(ifd[1]); 264 close(ifd[1]); 265 266 /* the parent just waits for the child to exit */ 267 while((wpid = sys_waitpid(pid, &status, 0)) < 0) { 268 if(errno == EINTR) { 269 errno = 0; 270 continue; 271 } 272 break; 273 } 274 275 CatchChild(); 276 277 if (wpid != pid) { 278 DEBUG(2, ("waitpid(%d) : %s\n", (int)pid, strerror(errno))); 279 return -1; 280 } 281 282#if defined(WIFEXITED) && defined(WEXITSTATUS) 283 if (WIFEXITED(status)) { 284 return WEXITSTATUS(status); 285 } 286#endif 287 288 return status; 289 } 290 291 CatchChild(); 292 293 /* we are in the child. we exec /bin/sh to do the work for us. we 294 don't directly exec the command we want because it may be a 295 pipeline or anything else the config file specifies */ 296 297 close(ifd[1]); 298 close(0); 299 if (sys_dup2(ifd[0], 0) != 0) { 300 DEBUG(2,("Failed to create stdin file descriptor\n")); 301 close(ifd[0]); 302 exit(80); 303 } 304 305 /* now completely lose our privileges. This is a fairly paranoid 306 way of doing it, but it does work on all systems that I know of */ 307 308 become_user_permanently(uid, gid); 309 310 if (getuid() != uid || geteuid() != uid || 311 getgid() != gid || getegid() != gid) { 312 /* we failed to lose our privileges - do not execute 313 the command */ 314 exit(81); /* we can't print stuff at this stage, 315 instead use exit codes for debugging */ 316 } 317 318#ifndef __INSURE__ 319 /* close all other file descriptors, leaving only 0, 1 and 2. 0 and 320 2 point to /dev/null from the startup code */ 321 { 322 int fd; 323 for (fd = 3; fd < 256; fd++) close(fd); 324 } 325#endif 326 327 execl("/bin/sh", "sh", "-c", cmd, NULL); 328 329 /* not reached */ 330 exit(82); 331 return 1; 332} 333