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