merge from the autoconf2 branch to the main branch
[ira/wip.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.igroups = conn->igroups;
246     current_user.ngroups = conn->ngroups;
247   }
248   else
249   {
250     if (!vuser) {
251       DEBUG(2,("Invalid vuid used %d\n",vuid));
252       return(False);
253     }
254     uid = vuser->uid;
255     if(!*lp_force_group(snum))
256       gid = vuser->gid;
257     else
258       gid = conn->gid;
259     current_user.ngroups = vuser->n_groups;
260     current_user.groups  = vuser->groups;
261     current_user.igroups = vuser->igroups;
262   }
263
264   if (initial_uid == 0)
265     {
266       if (!become_gid(gid)) return(False);
267
268 #ifdef HAVE_SETGROUPS      
269       if (!(VALID_CNUM(cnum) && conn->ipc)) {
270         /* groups stuff added by ih/wreu */
271         if (current_user.ngroups > 0)
272           if (setgroups(current_user.ngroups,current_user.groups)<0)
273             DEBUG(0,("setgroups call failed!\n"));
274       }
275 #endif
276
277       if (!conn->admin_user && !become_uid(uid))
278         return(False);
279     }
280
281   current_user.cnum = cnum;
282   current_user.vuid = vuid;
283
284   DEBUG(5,("become_user uid=(%d,%d) gid=(%d,%d)\n",
285            getuid(),geteuid(),getgid(),getegid()));
286   
287   return(True);
288 }
289
290 /****************************************************************************
291   unbecome the user of a connection number
292 ****************************************************************************/
293 BOOL unbecome_user(void )
294 {
295   if (current_user.cnum == -1)
296     return(False);
297
298   ChDir(OriginalDir);
299
300   if (initial_uid == 0)
301     {
302 #ifdef HAVE_SETRESUID
303       setresuid(-1,getuid(),-1);
304       setresgid(-1,getgid(),-1);
305 #else
306       if (seteuid(initial_uid) != 0) 
307         setuid(initial_uid);
308       setgid(initial_gid);
309 #endif
310     }
311
312 #ifdef NO_EID
313   if (initial_uid == 0)
314     DEBUG(2,("Running with no EID\n"));
315   initial_uid = getuid();
316   initial_gid = getgid();
317 #else
318   if (geteuid() != initial_uid) {
319           DEBUG(0,("Warning: You appear to have a trapdoor uid system\n"));
320           initial_uid = geteuid();
321   }
322   if (getegid() != initial_gid) {
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 HAVE_SETRESUID
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 #ifndef HAVE_EXECL
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 HAVE_SETRESUID
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 #ifdef HAVE_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 }