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