Makefile:
[bbaumbach/samba-autobuild/.git] / source / smbd / uid.c
1 /* 
2    Unix SMB/Netbios implementation.
3    Version 1.9.
4    uid/user handling
5    Copyright (C) Andrew Tridgell 1992-1997
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)\n"));
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   {
232     uid = Connections[cnum].uid;
233     gid = Connections[cnum].gid;
234     current_user.groups = Connections[cnum].groups;
235     current_user.igroups = Connections[cnum].igroups;
236     current_user.ngroups = Connections[cnum].ngroups;
237     current_user.attrs   = vuser->attrs;
238   }
239   else
240   {
241     if (!vuser) {
242       DEBUG(2,("Invalid vuid used %d\n",vuid));
243       return(False);
244     }
245     uid = vuser->uid;
246     if(!*lp_force_group(snum))
247       gid = vuser->gid;
248     else
249       gid = Connections[cnum].gid;
250     current_user.ngroups = vuser->n_groups;
251     current_user.groups  = vuser->groups;
252     current_user.igroups = vuser->igroups;
253     current_user.attrs   = vuser->attrs;
254   }
255
256   if (initial_uid == 0)
257     {
258       if (!become_gid(gid)) return(False);
259
260 #ifndef NO_SETGROUPS      
261       if (!IS_IPC(cnum)) {
262         /* groups stuff added by ih/wreu */
263         if (current_user.ngroups > 0)
264           if (setgroups(current_user.ngroups,current_user.groups)<0)
265             DEBUG(0,("setgroups call failed!\n"));
266       }
267 #endif
268
269       if (!Connections[cnum].admin_user && !become_uid(uid))
270         return(False);
271     }
272
273   current_user.cnum = cnum;
274   current_user.id = uid;
275   
276   DEBUG(5,("become_user uid=(%d,%d) gid=(%d,%d)\n",
277            getuid(),geteuid(),getgid(),getegid()));
278   
279   return(True);
280 }
281
282 /****************************************************************************
283   unbecome the user of a connection number
284 ****************************************************************************/
285 BOOL unbecome_user(void )
286 {
287   if (current_user.cnum == -1)
288     return(False);
289
290   ChDir(OriginalDir);
291
292   if (initial_uid == 0)
293     {
294 #ifdef USE_SETRES
295       setresuid(-1,getuid(),-1);
296       setresgid(-1,getgid(),-1);
297 #elif defined(USE_SETFS)
298       setfsuid(initial_uid);
299       setfsgid(initial_gid);
300 #else
301       if (seteuid(initial_uid) != 0) 
302         setuid(initial_uid);
303       setgid(initial_gid);
304 #endif
305     }
306 #ifdef NO_EID
307   if (initial_uid == 0)
308     DEBUG(2,("Running with no EID\n"));
309   initial_uid = getuid();
310   initial_gid = getgid();
311 #else
312   if (geteuid() != initial_uid)
313     {
314       DEBUG(0,("Warning: You appear to have a trapdoor uid system\n"));
315       initial_uid = geteuid();
316     }
317   if (getegid() != initial_gid)
318     {
319       DEBUG(0,("Warning: You appear to have a trapdoor gid system\n"));
320       initial_gid = getegid();
321     }
322 #endif
323
324   current_user.uid = initial_uid;
325   current_user.gid = initial_gid;
326   
327   if (ChDir(OriginalDir) != 0)
328     DEBUG(0,("%s chdir(%s) failed in unbecome_user\n",
329              timestring(),OriginalDir));
330
331   DEBUG(5,("unbecome_user now uid=(%d,%d) gid=(%d,%d)\n",
332         getuid(),geteuid(),getgid(),getegid()));
333
334   current_user.cnum = -1;
335
336   return(True);
337 }
338
339
340 /****************************************************************************
341 This is a utility function of smbrun(). It must be called only from
342 the child as it may leave the caller in a privilaged state.
343 ****************************************************************************/
344 static BOOL setup_stdout_file(char *outfile,BOOL shared)
345 {  
346   int fd;
347   struct stat st;
348   mode_t mode = S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH;
349   int flags = O_RDWR|O_CREAT|O_TRUNC|O_EXCL;
350
351   close(1);
352
353   if (shared) {
354     /* become root - unprivilaged users can't delete these files */
355 #ifdef USE_SETRES
356     setresgid(0,0,0);
357     setresuid(0,0,0);
358 #else      
359     setuid(0);
360     seteuid(0);
361 #endif
362   }
363
364   if(stat(outfile, &st) == 0) {
365     /* Check we're not deleting a device file. */ 
366     if(st.st_mode & S_IFREG)
367       unlink(outfile);
368     else
369       flags = O_RDWR;
370   }
371   /* now create the file */
372   fd = open(outfile,flags,mode);
373
374   if (fd == -1) return False;
375
376   if (fd != 1) {
377     if (dup2(fd,1) != 0) {
378       DEBUG(2,("Failed to create stdout file descriptor\n"));
379       close(fd);
380       return False;
381     }
382     close(fd);
383   }
384   return True;
385 }
386
387
388 /****************************************************************************
389 run a command being careful about uid/gid handling and putting the output in
390 outfile (or discard it if outfile is NULL).
391
392 if shared is True then ensure the file will be writeable by all users
393 but created such that its owned by root. This overcomes a security hole.
394
395 if shared is not set then open the file with O_EXCL set
396 ****************************************************************************/
397 int smbrun(char *cmd,char *outfile,BOOL shared)
398 {
399   int fd,pid;
400   int uid = current_user.uid;
401   int gid = current_user.gid;
402
403 #if USE_SYSTEM
404   int ret;
405   pstring syscmd;  
406   char *path = lp_smbrun();
407
408   /* in the old method we use system() to execute smbrun which then
409      executes the command (using system() again!). This involves lots
410      of shell launches and is very slow. It also suffers from a
411      potential security hole */
412   if (!file_exist(path,NULL))
413     {
414       DEBUG(0,("SMBRUN ERROR: Can't find %s. Installation problem?\n",path));
415       return(1);
416     }
417
418   sprintf(syscmd,"%s %d %d \"(%s 2>&1) > %s\"",
419           path,uid,gid,cmd,
420           outfile?outfile:"/dev/null");
421
422   DEBUG(5,("smbrun - running %s ",syscmd));
423   ret = system(syscmd);
424   DEBUG(5,("gave %d\n",ret));
425   return(ret);
426 #else
427   /* in this newer method we will exec /bin/sh with the correct
428      arguments, after first setting stdout to point at the file */
429
430   if ((pid=fork())) {
431     int status=0;
432     /* the parent just waits for the child to exit */
433     if (sys_waitpid(pid,&status,0) != pid) {
434       DEBUG(2,("waitpid(%d) : %s\n",pid,strerror(errno)));
435       return -1;
436     }
437     return status;
438   }
439
440
441   /* we are in the child. we exec /bin/sh to do the work for us. we
442      don't directly exec the command we want because it may be a
443      pipeline or anything else the config file specifies */
444
445   /* point our stdout at the file we want output to go into */
446   if (outfile && !setup_stdout_file(outfile,shared)) {
447     exit(80);
448   }
449
450   /* now completely lose our privilages. This is a fairly paranoid
451      way of doing it, but it does work on all systems that I know of */
452 #ifdef USE_SETRES
453   setresgid(0,0,0);
454   setresuid(0,0,0);
455   setresgid(gid,gid,gid);
456   setresuid(uid,uid,uid);      
457 #else      
458   setuid(0);
459   seteuid(0);
460   setgid(gid);
461   setegid(gid);
462   setuid(uid);
463   seteuid(uid);
464 #endif
465
466   if (getuid() != uid || geteuid() != uid ||
467       getgid() != gid || getegid() != gid) {
468     /* we failed to lose our privilages - do not execute the command */
469     exit(81); /* we can't print stuff at this stage, instead use exit codes
470                  for debugging */
471   }
472
473   /* close all other file descriptors, leaving only 0, 1 and 2. 0 and
474      2 point to /dev/null from the startup code */
475   for (fd=3;fd<256;fd++) close(fd);
476
477   execl("/bin/sh","sh","-c",cmd,NULL);  
478
479   /* not reached */
480   exit(82);
481 #endif
482   return 1;
483 }