Changed become_user to take a vuid as second arg.
[samba.git] / source3 / smbd / uid.c
1 /* 
2    Unix SMB/Netbios implementation.
3    Version 1.9.
4    uid/user handling
5    Copyright (C) Andrew Tridgell 1992-1995
6    
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 #include "includes.h"
23
24 extern int DEBUGLEVEL;
25
26 extern connection_struct Connections[];
27
28 static int initial_uid;
29 static int initial_gid;
30
31 /* what user is current? */
32 struct current_user current_user;
33
34 extern pstring OriginalDir;
35
36 /****************************************************************************
37 initialise the uid routines
38 ****************************************************************************/
39 void init_uid(void)
40 {
41   initial_uid = current_user.uid = geteuid();
42   initial_gid = current_user.gid = getegid();
43
44   if (initial_gid != 0 && initial_uid == 0)
45     {
46 #ifdef HPUX
47       setresgid(0,0,0);
48 #else
49       setgid(0);
50       setegid(0);
51 #endif
52     }
53
54   initial_uid = geteuid();
55   initial_gid = getegid();
56
57   current_user.cnum = -1;
58
59   ChDir(OriginalDir);
60 }
61
62
63 /****************************************************************************
64   become the specified uid 
65 ****************************************************************************/
66 static BOOL become_uid(int uid)
67 {
68   if (initial_uid != 0)
69     return(True);
70
71   if (uid == -1 || uid == 65535) {
72     DEBUG(1,("WARNING: using uid %d is a security risk\n",uid));    
73   }
74
75 #ifdef AIX
76   {
77     /* AIX 3 stuff - inspired by a code fragment in wu-ftpd */
78     priv_t priv;
79
80     priv.pv_priv[0] = 0;
81     priv.pv_priv[1] = 0;
82     if (setpriv(PRIV_SET|PRIV_INHERITED|PRIV_EFFECTIVE|PRIV_BEQUEATH,
83                 &priv, sizeof(priv_t)) < 0 ||
84         setuidx(ID_REAL|ID_EFFECTIVE, (uid_t)uid) < 0 ||
85         seteuid((uid_t)uid) < 0) 
86       DEBUG(1,("Can't set uid (AIX3)"));
87   }
88 #endif
89
90 #ifdef USE_SETRES
91   if (setresuid(-1,uid,-1) != 0)
92 #elif defined(USE_SETFS)
93     if (setfsuid(uid) != 0)
94 #else
95     if ((seteuid(uid) != 0) && 
96         (setuid(uid) != 0))
97 #endif
98       {
99         DEBUG(0,("Couldn't set uid %d currently set to (%d,%d)\n",
100                  uid,getuid(), geteuid()));
101         if (uid > 32000)
102           DEBUG(0,("Looks like your OS doesn't like high uid values - try using a different account\n"));
103         return(False);
104       }
105
106   if (((uid == -1) || (uid == 65535)) && geteuid() != uid) {
107     DEBUG(0,("Invalid uid -1. perhaps you have a account with uid 65535?\n"));
108     return(False);
109   }
110
111   current_user.uid = uid;
112
113   return(True);
114 }
115
116
117 /****************************************************************************
118   become the specified gid
119 ****************************************************************************/
120 static BOOL become_gid(int gid)
121 {
122   if (initial_uid != 0)
123     return(True);
124
125   if (gid == -1 || gid == 65535) {
126     DEBUG(1,("WARNING: using gid %d is a security risk\n",gid));    
127   }
128   
129 #ifdef USE_SETRES 
130   if (setresgid(-1,gid,-1) != 0)
131 #elif defined(USE_SETFS)
132   if (setfsgid(gid) != 0)
133 #else
134   if (setgid(gid) != 0)
135 #endif
136       {
137         DEBUG(0,("Couldn't set gid %d currently set to (%d,%d)\n",
138                  gid,getgid(),getegid()));
139         if (gid > 32000)
140           DEBUG(0,("Looks like your OS doesn't like high gid values - try using a different account\n"));
141         return(False);
142       }
143
144   current_user.gid = gid;
145
146   return(True);
147 }
148
149
150 /****************************************************************************
151   become the specified uid and gid
152 ****************************************************************************/
153 static BOOL become_id(int uid,int gid)
154 {
155   return(become_gid(gid) && become_uid(uid));
156 }
157
158 /****************************************************************************
159 become the guest user
160 ****************************************************************************/
161 BOOL become_guest(void)
162 {
163   BOOL ret;
164   static struct passwd *pass=NULL;
165
166   if (initial_uid != 0) 
167     return(True);
168
169   if (!pass)
170     pass = Get_Pwnam(lp_guestaccount(-1),True);
171   if (!pass) return(False);
172
173   ret = become_id(pass->pw_uid,pass->pw_gid);
174
175   if (!ret)
176     DEBUG(1,("Failed to become guest. Invalid guest account?\n"));
177
178   current_user.cnum = -2;
179
180   return(ret);
181 }
182
183 /*******************************************************************
184 check if a username is OK
185 ********************************************************************/
186 static BOOL check_user_ok(int cnum,user_struct *vuser,int snum)
187 {
188   int i;
189   for (i=0;i<Connections[cnum].uid_cache.entries;i++)
190     if (Connections[cnum].uid_cache.list[i] == vuser->uid) return(True);
191
192   if (!user_ok(vuser->name,snum)) return(False);
193
194   i = Connections[cnum].uid_cache.entries % UID_CACHE_SIZE;
195   Connections[cnum].uid_cache.list[i] = vuser->uid;
196
197   if (Connections[cnum].uid_cache.entries < UID_CACHE_SIZE)
198     Connections[cnum].uid_cache.entries++;
199
200   return(True);
201 }
202
203
204 /****************************************************************************
205   become the user of a connection number
206 ****************************************************************************/
207 BOOL become_user(int cnum, uint16 vuid)
208 {
209   user_struct *vuser = get_valid_user_struct(vuid);
210   int snum,gid;
211   int uid;
212
213   if (current_user.cnum == cnum && vuser != 0 && current_user.id == vuser->uid) {
214     DEBUG(4,("Skipping become_user - already user\n"));
215     return(True);
216   }
217
218   unbecome_user();
219
220   if (!OPEN_CNUM(cnum)) {
221     DEBUG(2,("Connection %d not open\n",cnum));
222     return(False);
223   }
224
225   snum = Connections[cnum].service;
226
227   if (Connections[cnum].force_user || 
228       lp_security() == SEC_SHARE ||
229       !(vuser) || (vuser->guest) ||
230       !check_user_ok(cnum,vuser,snum)) {
231     uid = Connections[cnum].uid;
232     gid = Connections[cnum].gid;
233     current_user.groups = Connections[cnum].groups;
234     current_user.igroups = Connections[cnum].igroups;
235     current_user.ngroups = Connections[cnum].ngroups;
236   } else {
237     if (!vuser) {
238       DEBUG(2,("Invalid vuid used %d\n",vuid));
239       return(False);
240     }
241     uid = vuser->uid;
242     if(!*lp_force_group(snum))
243       gid = vuser->gid;
244     else
245       gid = Connections[cnum].gid;
246     current_user.groups = vuser->user_groups;
247     current_user.igroups = vuser->user_igroups;
248     current_user.ngroups = vuser->user_ngroups;
249   }
250
251   if (initial_uid == 0)
252     {
253       if (!become_gid(gid)) return(False);
254
255 #ifndef NO_SETGROUPS      
256       if (!IS_IPC(cnum)) {
257         /* groups stuff added by ih/wreu */
258         if (current_user.ngroups > 0)
259           if (setgroups(current_user.ngroups,current_user.groups)<0)
260             DEBUG(0,("setgroups call failed!\n"));
261       }
262 #endif
263
264       if (!Connections[cnum].admin_user && !become_uid(uid))
265         return(False);
266     }
267
268   current_user.cnum = cnum;
269   current_user.id = uid;
270   
271   DEBUG(5,("become_user uid=(%d,%d) gid=(%d,%d)\n",
272            getuid(),geteuid(),getgid(),getegid()));
273   
274   return(True);
275 }
276
277 /****************************************************************************
278   unbecome the user of a connection number
279 ****************************************************************************/
280 BOOL unbecome_user(void )
281 {
282   if (current_user.cnum == -1)
283     return(False);
284
285   ChDir(OriginalDir);
286
287   if (initial_uid == 0)
288     {
289 #ifdef USE_SETRES
290       setresuid(-1,getuid(),-1);
291       setresgid(-1,getgid(),-1);
292 #elif defined(USE_SETFS)
293       setfsuid(initial_uid);
294       setfsgid(initial_gid);
295 #else
296       if (seteuid(initial_uid) != 0) 
297         setuid(initial_uid);
298       setgid(initial_gid);
299 #endif
300     }
301 #ifdef NO_EID
302   if (initial_uid == 0)
303     DEBUG(2,("Running with no EID\n"));
304   initial_uid = getuid();
305   initial_gid = getgid();
306 #else
307   if (geteuid() != initial_uid)
308     {
309       DEBUG(0,("Warning: You appear to have a trapdoor uid system\n"));
310       initial_uid = geteuid();
311     }
312   if (getegid() != initial_gid)
313     {
314       DEBUG(0,("Warning: You appear to have a trapdoor gid system\n"));
315       initial_gid = getegid();
316     }
317 #endif
318
319   current_user.uid = initial_uid;
320   current_user.gid = initial_gid;
321   
322   if (ChDir(OriginalDir) != 0)
323     DEBUG(0,("%s chdir(%s) failed in unbecome_user\n",
324              timestring(),OriginalDir));
325
326   DEBUG(5,("unbecome_user now uid=(%d,%d) gid=(%d,%d)\n",
327         getuid(),geteuid(),getgid(),getegid()));
328
329   current_user.cnum = -1;
330
331   return(True);
332 }
333
334
335 /****************************************************************************
336 This is a utility function of smbrun(). It must be called only from
337 the child as it may leave the caller in a privilaged state.
338 ****************************************************************************/
339 static BOOL setup_stdout_file(char *outfile,BOOL shared)
340 {  
341   int fd;
342   mode_t mode = S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH;
343
344   close(1);
345
346   if (shared) {
347     /* become root - unprivilaged users can't delete these files */
348 #ifdef USE_SETRES
349     setresgid(0,0,0);
350     setresuid(0,0,0);
351 #else      
352     setuid(0);
353     seteuid(0);
354 #endif
355   }
356
357   /* now create the file with O_EXCL set */
358   unlink(outfile);
359   fd = open(outfile,O_RDWR|O_CREAT|O_TRUNC|O_EXCL,mode);
360
361   if (fd == -1) return False;
362
363   if (fd != 1) {
364     if (dup2(fd,1) != 0) {
365       DEBUG(2,("Failed to create stdout file descriptor\n"));
366       close(fd);
367       return False;
368     }
369     close(fd);
370   }
371   return True;
372 }
373
374
375 /****************************************************************************
376 run a command being careful about uid/gid handling and putting the output in
377 outfile (or discard it if outfile is NULL).
378
379 if shared is True then ensure the file will be writeable by all users
380 but created such that its owned by root. This overcomes a security hole.
381
382 if shared is not set then open the file with O_EXCL set
383 ****************************************************************************/
384 int smbrun(char *cmd,char *outfile,BOOL shared)
385 {
386   int fd,pid;
387   int uid = current_user.uid;
388   int gid = current_user.gid;
389
390 #if USE_SYSTEM
391   int ret;
392   pstring syscmd;  
393   char *path = lp_smbrun();
394
395   /* in the old method we use system() to execute smbrun which then
396      executes the command (using system() again!). This involves lots
397      of shell launches and is very slow. It also suffers from a
398      potential security hole */
399   if (!file_exist(path,NULL))
400     {
401       DEBUG(0,("SMBRUN ERROR: Can't find %s. Installation problem?\n",path));
402       return(1);
403     }
404
405   sprintf(syscmd,"%s %d %d \"(%s 2>&1) > %s\"",
406           path,uid,gid,cmd,
407           outfile?outfile:"/dev/null");
408
409   DEBUG(5,("smbrun - running %s ",syscmd));
410   ret = system(syscmd);
411   DEBUG(5,("gave %d\n",ret));
412   return(ret);
413 #else
414   /* in this newer method we will exec /bin/sh with the correct
415      arguments, after first setting stdout to point at the file */
416
417   if ((pid=fork())) {
418     int status=0;
419     /* the parent just waits for the child to exit */
420     if (sys_waitpid(pid,&status,0) != pid) {
421       DEBUG(2,("waitpid(%d) : %s\n",pid,strerror(errno)));
422       return -1;
423     }
424     return status;
425   }
426
427
428   /* we are in the child. we exec /bin/sh to do the work for us. we
429      don't directly exec the command we want because it may be a
430      pipeline or anything else the config file specifies */
431
432   /* point our stdout at the file we want output to go into */
433   if (outfile && !setup_stdout_file(outfile,shared)) {
434     exit(80);
435   }
436
437   /* now completely lose our privilages. This is a fairly paranoid
438      way of doing it, but it does work on all systems that I know of */
439 #ifdef USE_SETRES
440   setresgid(0,0,0);
441   setresuid(0,0,0);
442   setresgid(gid,gid,gid);
443   setresuid(uid,uid,uid);      
444 #else      
445   setuid(0);
446   seteuid(0);
447   setgid(gid);
448   setegid(gid);
449   setuid(uid);
450   seteuid(uid);
451 #endif
452
453   if (getuid() != uid || geteuid() != uid ||
454       getgid() != gid || getegid() != gid) {
455     /* we failed to lose our privilages - do not execute the command */
456     exit(81); /* we can't print stuff at this stage, instead use exit codes
457                  for debugging */
458   }
459
460   /* close all other file descriptors, leaving only 0, 1 and 2. 0 and
461      2 point to /dev/null from the startup code */
462   for (fd=3;fd<256;fd++) close(fd);
463
464   execl("/bin/sh","sh","-c",cmd,NULL);  
465
466   /* not reached */
467   exit(82);
468 #endif
469 }
470
471