get rid of the runtime test for broken getgroups() and add a compile
[kai/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-1998
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 static int initial_uid;
27 static int initial_gid;
28
29 /* what user is current? */
30 struct current_user current_user;
31
32 pstring OriginalDir;
33
34 /****************************************************************************
35 initialise the uid routines
36 ****************************************************************************/
37 void init_uid(void)
38 {
39         initial_uid = current_user.uid = geteuid();
40         initial_gid = current_user.gid = getegid();
41
42         if (initial_gid != 0 && initial_uid == 0) {
43 #ifdef HAVE_SETRESUID
44                 setresgid(0,0,0);
45 #else
46                 setgid(0);
47                 setegid(0);
48 #endif
49         }
50
51         initial_uid = geteuid();
52         initial_gid = getegid();
53
54         current_user.cnum = -1;
55         current_user.vuid = UID_FIELD_INVALID;
56         
57         ChDir(OriginalDir);
58 }
59
60
61 /****************************************************************************
62   become the specified uid 
63 ****************************************************************************/
64 static BOOL become_uid(int uid)
65 {
66         if (initial_uid != 0) {
67                 return(True);
68         }
69         
70         if (uid == -1 || uid == 65535) {
71                 static int done;
72                 if (!done) {
73                         DEBUG(1,("WARNING: using uid %d is a security risk\n",
74                                  uid));
75                         done=1;
76                 }
77         }
78
79 #ifdef HAVE_TRAPDOOR_UID
80 #ifdef HAVE_SETUIDX
81         /* AIX3 has setuidx which is NOT a trapoor function (tridge) */
82         if (setuidx(ID_EFFECTIVE, (uid_t)uid) != 0) {
83                 if (seteuid((uid_t)uid) != 0) {
84                         DEBUG(1,("Can't set uid (setuidx)\n"));
85                         return False;
86                 }
87         }
88 #endif
89 #endif
90
91 #ifdef HAVE_SETRESUID
92     if (setresuid(-1,uid,-1) != 0)
93 #else
94     if ((seteuid(uid) != 0) && 
95         (setuid(uid) != 0))
96 #endif
97       {
98         DEBUG(0,("Couldn't set uid %d currently set to (%d,%d)\n",
99                  uid,getuid(), geteuid()));
100         if (uid > 32000) {
101                 DEBUG(0,("Looks like your OS doesn't like high uid values - try using a different account\n"));
102         }
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 HAVE_SETRESUID
130   if (setresgid(-1,gid,-1) != 0)
131 #else
132   if (setgid(gid) != 0)
133 #endif
134       {
135         DEBUG(0,("Couldn't set gid %d currently set to (%d,%d)\n",
136                  gid,getgid(),getegid()));
137         if (gid > 32000) {
138                 DEBUG(0,("Looks like your OS doesn't like high gid values - try using a different account\n"));
139         }
140         return(False);
141       }
142
143   current_user.gid = gid;
144
145   return(True);
146 }
147
148
149 /****************************************************************************
150   become the specified uid and gid
151 ****************************************************************************/
152 static BOOL become_id(int uid,int gid)
153 {
154         return(become_gid(gid) && become_uid(uid));
155 }
156
157 /****************************************************************************
158 become the guest user
159 ****************************************************************************/
160 BOOL become_guest(void)
161 {
162   BOOL ret;
163   static struct passwd *pass=NULL;
164
165   if (initial_uid != 0) 
166     return(True);
167
168   if (!pass)
169     pass = Get_Pwnam(lp_guestaccount(-1),True);
170   if (!pass) return(False);
171
172 #ifdef AIX
173   /* MWW: From AIX FAQ patch to WU-ftpd: call initgroups before setting IDs */
174   initgroups(pass->pw_name, (gid_t)pass->pw_gid);
175 #endif
176
177   ret = become_id(pass->pw_uid,pass->pw_gid);
178
179   if (!ret) {
180     DEBUG(1,("Failed to become guest. Invalid guest account?\n"));
181   }
182
183   current_user.cnum = -2;
184   current_user.vuid = UID_FIELD_INVALID;
185
186   return(ret);
187 }
188
189 /*******************************************************************
190 check if a username is OK
191 ********************************************************************/
192 static BOOL check_user_ok(connection_struct *conn, user_struct *vuser,int snum)
193 {
194   int i;
195   for (i=0;i<conn->uid_cache.entries;i++)
196     if (conn->uid_cache.list[i] == vuser->uid) return(True);
197
198   if (!user_ok(vuser->name,snum)) return(False);
199
200   i = conn->uid_cache.entries % UID_CACHE_SIZE;
201   conn->uid_cache.list[i] = vuser->uid;
202
203   if (conn->uid_cache.entries < UID_CACHE_SIZE)
204     conn->uid_cache.entries++;
205
206   return(True);
207 }
208
209
210 /****************************************************************************
211   become the user of a connection number
212 ****************************************************************************/
213 BOOL become_user(connection_struct *conn, int cnum, uint16 vuid)
214 {
215   user_struct *vuser = get_valid_user_struct(vuid);
216   int snum,gid;
217   int uid;
218
219   if ((current_user.cnum == cnum) && (vuser != 0) && (current_user.vuid == vuid) && 
220       (current_user.uid == vuser->uid)) {
221     DEBUG(4,("Skipping become_user - already user\n"));
222     return(True);
223   }
224
225   unbecome_user();
226
227   if (!(VALID_CNUM(cnum) && conn->open)) {
228     DEBUG(2,("Connection %d not open\n",cnum));
229     return(False);
230   }
231
232   snum = conn->service;
233
234   if((vuser != NULL) && !check_user_ok(conn, vuser, snum))
235     return False;
236
237   if (conn->force_user || 
238       lp_security() == SEC_SHARE ||
239       !(vuser) || (vuser->guest)
240      )
241   {
242     uid = conn->uid;
243     gid = conn->gid;
244     current_user.groups = conn->groups;
245     current_user.ngroups = conn->ngroups;
246   }
247   else
248   {
249     if (!vuser) {
250       DEBUG(2,("Invalid vuid used %d\n",vuid));
251       return(False);
252     }
253     uid = vuser->uid;
254     if(!*lp_force_group(snum))
255       gid = vuser->gid;
256     else
257       gid = conn->gid;
258     current_user.ngroups = vuser->n_groups;
259     current_user.groups  = vuser->groups;
260   }
261
262   if (initial_uid == 0)
263     {
264       if (!become_gid(gid)) return(False);
265
266 #ifdef HAVE_SETGROUPS      
267       if (!(VALID_CNUM(cnum) && conn->ipc)) {
268         /* groups stuff added by ih/wreu */
269         if (current_user.ngroups > 0)
270           if (setgroups(current_user.ngroups,current_user.groups)<0)
271             DEBUG(0,("setgroups call failed!\n"));
272       }
273 #endif
274
275       if (!conn->admin_user && !become_uid(uid))
276         return(False);
277     }
278
279   current_user.cnum = cnum;
280   current_user.vuid = vuid;
281
282   DEBUG(5,("become_user uid=(%d,%d) gid=(%d,%d)\n",
283            getuid(),geteuid(),getgid(),getegid()));
284   
285   return(True);
286 }
287
288 /****************************************************************************
289   unbecome the user of a connection number
290 ****************************************************************************/
291 BOOL unbecome_user(void )
292 {
293   if (current_user.cnum == -1)
294     return(False);
295
296   ChDir(OriginalDir);
297
298   if (initial_uid == 0)
299     {
300 #ifdef HAVE_SETRESUID
301       setresuid(-1,getuid(),-1);
302       setresgid(-1,getgid(),-1);
303 #else
304       if (seteuid(initial_uid) != 0) 
305         setuid(initial_uid);
306       setgid(initial_gid);
307 #endif
308     }
309
310 #ifdef NO_EID
311   if (initial_uid == 0)
312     DEBUG(2,("Running with no EID\n"));
313   initial_uid = getuid();
314   initial_gid = getgid();
315 #else
316   if (geteuid() != initial_uid) {
317           DEBUG(0,("Warning: You appear to have a trapdoor uid system\n"));
318           initial_uid = geteuid();
319   }
320   if (getegid() != initial_gid) {
321           DEBUG(0,("Warning: You appear to have a trapdoor gid system\n"));
322           initial_gid = getegid();
323   }
324 #endif
325
326   current_user.uid = initial_uid;
327   current_user.gid = initial_gid;
328   
329   if (ChDir(OriginalDir) != 0)
330     DEBUG(0,("%s chdir(%s) failed in unbecome_user\n",
331              timestring(),OriginalDir));
332
333   DEBUG(5,("unbecome_user now uid=(%d,%d) gid=(%d,%d)\n",
334         getuid(),geteuid(),getgid(),getegid()));
335
336   current_user.cnum = -1;
337   current_user.vuid = UID_FIELD_INVALID;
338
339   return(True);
340 }
341
342
343 /****************************************************************************
344 This is a utility function of smbrun(). It must be called only from
345 the child as it may leave the caller in a privilaged state.
346 ****************************************************************************/
347 static BOOL setup_stdout_file(char *outfile,BOOL shared)
348 {  
349   int fd;
350   struct stat st;
351   mode_t mode = S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH;
352   int flags = O_RDWR|O_CREAT|O_TRUNC|O_EXCL;
353
354   close(1);
355
356   if (shared) {
357           /* become root - unprivilaged users can't delete these files */
358 #ifdef HAVE_SETRESUID
359           setresgid(0,0,0);
360           setresuid(0,0,0);
361 #else      
362           setuid(0);
363           seteuid(0);
364 #endif
365   }
366
367   if(stat(outfile, &st) == 0) {
368     /* Check we're not deleting a device file. */ 
369     if(st.st_mode & S_IFREG)
370       unlink(outfile);
371     else
372       flags = O_RDWR;
373   }
374   /* now create the file */
375   fd = open(outfile,flags,mode);
376
377   if (fd == -1) return False;
378
379   if (fd != 1) {
380     if (dup2(fd,1) != 0) {
381       DEBUG(2,("Failed to create stdout file descriptor\n"));
382       close(fd);
383       return False;
384     }
385     close(fd);
386   }
387   return True;
388 }
389
390
391 /****************************************************************************
392 run a command being careful about uid/gid handling and putting the output in
393 outfile (or discard it if outfile is NULL).
394
395 if shared is True then ensure the file will be writeable by all users
396 but created such that its owned by root. This overcomes a security hole.
397
398 if shared is not set then open the file with O_EXCL set
399 ****************************************************************************/
400 int smbrun(char *cmd,char *outfile,BOOL shared)
401 {
402   int fd,pid;
403   int uid = current_user.uid;
404   int gid = current_user.gid;
405
406 #ifndef HAVE_EXECL
407   int ret;
408   pstring syscmd;  
409   char *path = lp_smbrun();
410
411   /* in the old method we use system() to execute smbrun which then
412      executes the command (using system() again!). This involves lots
413      of shell launches and is very slow. It also suffers from a
414      potential security hole */
415   if (!file_exist(path,NULL))
416     {
417       DEBUG(0,("SMBRUN ERROR: Can't find %s. Installation problem?\n",path));
418       return(1);
419     }
420
421   slprintf(syscmd,sizeof(syscmd)-1,"%s %d %d \"(%s 2>&1) > %s\"",
422           path,uid,gid,cmd,
423           outfile?outfile:"/dev/null");
424
425   DEBUG(5,("smbrun - running %s ",syscmd));
426   ret = system(syscmd);
427   DEBUG(5,("gave %d\n",ret));
428   return(ret);
429 #else
430   /* in this newer method we will exec /bin/sh with the correct
431      arguments, after first setting stdout to point at the file */
432
433   if ((pid=fork())) {
434     int status=0;
435     /* the parent just waits for the child to exit */
436     if (sys_waitpid(pid,&status,0) != pid) {
437       DEBUG(2,("waitpid(%d) : %s\n",pid,strerror(errno)));
438       return -1;
439     }
440     return status;
441   }
442
443
444   /* we are in the child. we exec /bin/sh to do the work for us. we
445      don't directly exec the command we want because it may be a
446      pipeline or anything else the config file specifies */
447
448   /* point our stdout at the file we want output to go into */
449   if (outfile && !setup_stdout_file(outfile,shared)) {
450     exit(80);
451   }
452
453   /* now completely lose our privilages. This is a fairly paranoid
454      way of doing it, but it does work on all systems that I know of */
455 #ifdef HAVE_SETRESUID
456   setresgid(0,0,0);
457   setresuid(0,0,0);
458   setresgid(gid,gid,gid);
459   setresuid(uid,uid,uid);      
460 #else      
461   setuid(0);
462   seteuid(0);
463   setgid(gid);
464   setegid(gid);
465   setuid(uid);
466   seteuid(uid);
467 #endif
468
469   if (getuid() != uid || geteuid() != uid ||
470       getgid() != gid || getegid() != gid) {
471     /* we failed to lose our privilages - do not execute the command */
472     exit(81); /* we can't print stuff at this stage, instead use exit codes
473                  for debugging */
474   }
475
476   /* close all other file descriptors, leaving only 0, 1 and 2. 0 and
477      2 point to /dev/null from the startup code */
478   for (fd=3;fd<256;fd++) close(fd);
479
480   execl("/bin/sh","sh","-c",cmd,NULL);  
481
482   /* not reached */
483   exit(82);
484 #endif
485   return 1;
486 }
487
488 static struct current_user current_user_saved;
489 static int become_root_depth;
490 static pstring become_root_dir;
491
492 /****************************************************************************
493 This is used when we need to do a privilaged operation (such as mucking
494 with share mode files) and temporarily need root access to do it. This
495 call should always be paired with an unbecome_root() call immediately
496 after the operation
497
498 Set save_dir if you also need to save/restore the CWD 
499 ****************************************************************************/
500 void become_root(BOOL save_dir) 
501 {
502         if (become_root_depth) {
503                 DEBUG(0,("ERROR: become root depth is non zero\n"));
504         }
505         if (save_dir)
506                 GetWd(become_root_dir);
507
508         current_user_saved = current_user;
509         become_root_depth = 1;
510
511         become_uid(0);
512         become_gid(0);
513 }
514
515 /****************************************************************************
516 When the privilaged operation is over call this
517
518 Set save_dir if you also need to save/restore the CWD 
519 ****************************************************************************/
520 void unbecome_root(BOOL restore_dir)
521 {
522         if (become_root_depth != 1) {
523                 DEBUG(0,("ERROR: unbecome root depth is %d\n",
524                          become_root_depth));
525         }
526
527         /* we might have done a become_user() while running as root,
528            if we have then become root again in order to become 
529            non root! */
530         if (current_user.uid != 0) {
531                 become_uid(0);
532         }
533
534         /* restore our gid first */
535         if (!become_gid(current_user_saved.gid)) {
536                 DEBUG(0,("ERROR: Failed to restore gid\n"));
537                 exit_server("Failed to restore gid");
538         }
539
540 #ifdef HAVE_SETGROUPS      
541         if (current_user_saved.ngroups > 0) {
542                 if (setgroups(current_user_saved.ngroups,
543                               current_user_saved.groups)<0)
544                         DEBUG(0,("ERROR: setgroups call failed!\n"));
545         }
546 #endif
547
548         /* now restore our uid */
549         if (!become_uid(current_user_saved.uid)) {
550                 DEBUG(0,("ERROR: Failed to restore uid\n"));
551                 exit_server("Failed to restore uid");
552         }
553
554         if (restore_dir)
555                 ChDir(become_root_dir);
556
557         current_user = current_user_saved;
558
559         become_root_depth = 0;
560 }