get rid of the runtime test for broken getgroups() and add a compile
[Samba/gbeck.git] / source / smbd / uid.c
blob173fdaca03c72f34c24d2b6059b1b652c05bbbe2
1 /*
2 Unix SMB/Netbios implementation.
3 Version 1.9.
4 uid/user handling
5 Copyright (C) Andrew Tridgell 1992-1998
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.
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.
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.
22 #include "includes.h"
24 extern int DEBUGLEVEL;
26 static int initial_uid;
27 static int initial_gid;
29 /* what user is current? */
30 struct current_user current_user;
32 pstring OriginalDir;
34 /****************************************************************************
35 initialise the uid routines
36 ****************************************************************************/
37 void init_uid(void)
39 initial_uid = current_user.uid = geteuid();
40 initial_gid = current_user.gid = getegid();
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
51 initial_uid = geteuid();
52 initial_gid = getegid();
54 current_user.cnum = -1;
55 current_user.vuid = UID_FIELD_INVALID;
57 ChDir(OriginalDir);
61 /****************************************************************************
62 become the specified uid
63 ****************************************************************************/
64 static BOOL become_uid(int uid)
66 if (initial_uid != 0) {
67 return(True);
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;
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;
88 #endif
89 #endif
91 #ifdef HAVE_SETRESUID
92 if (setresuid(-1,uid,-1) != 0)
93 #else
94 if ((seteuid(uid) != 0) &&
95 (setuid(uid) != 0))
96 #endif
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"));
103 return(False);
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);
111 current_user.uid = uid;
113 return(True);
117 /****************************************************************************
118 become the specified gid
119 ****************************************************************************/
120 static BOOL become_gid(int gid)
122 if (initial_uid != 0)
123 return(True);
125 if (gid == -1 || gid == 65535) {
126 DEBUG(1,("WARNING: using gid %d is a security risk\n",gid));
129 #ifdef HAVE_SETRESUID
130 if (setresgid(-1,gid,-1) != 0)
131 #else
132 if (setgid(gid) != 0)
133 #endif
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"));
140 return(False);
143 current_user.gid = gid;
145 return(True);
149 /****************************************************************************
150 become the specified uid and gid
151 ****************************************************************************/
152 static BOOL become_id(int uid,int gid)
154 return(become_gid(gid) && become_uid(uid));
157 /****************************************************************************
158 become the guest user
159 ****************************************************************************/
160 BOOL become_guest(void)
162 BOOL ret;
163 static struct passwd *pass=NULL;
165 if (initial_uid != 0)
166 return(True);
168 if (!pass)
169 pass = Get_Pwnam(lp_guestaccount(-1),True);
170 if (!pass) return(False);
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
177 ret = become_id(pass->pw_uid,pass->pw_gid);
179 if (!ret) {
180 DEBUG(1,("Failed to become guest. Invalid guest account?\n"));
183 current_user.cnum = -2;
184 current_user.vuid = UID_FIELD_INVALID;
186 return(ret);
189 /*******************************************************************
190 check if a username is OK
191 ********************************************************************/
192 static BOOL check_user_ok(connection_struct *conn, user_struct *vuser,int snum)
194 int i;
195 for (i=0;i<conn->uid_cache.entries;i++)
196 if (conn->uid_cache.list[i] == vuser->uid) return(True);
198 if (!user_ok(vuser->name,snum)) return(False);
200 i = conn->uid_cache.entries % UID_CACHE_SIZE;
201 conn->uid_cache.list[i] = vuser->uid;
203 if (conn->uid_cache.entries < UID_CACHE_SIZE)
204 conn->uid_cache.entries++;
206 return(True);
210 /****************************************************************************
211 become the user of a connection number
212 ****************************************************************************/
213 BOOL become_user(connection_struct *conn, int cnum, uint16 vuid)
215 user_struct *vuser = get_valid_user_struct(vuid);
216 int snum,gid;
217 int uid;
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);
225 unbecome_user();
227 if (!(VALID_CNUM(cnum) && conn->open)) {
228 DEBUG(2,("Connection %d not open\n",cnum));
229 return(False);
232 snum = conn->service;
234 if((vuser != NULL) && !check_user_ok(conn, vuser, snum))
235 return False;
237 if (conn->force_user ||
238 lp_security() == SEC_SHARE ||
239 !(vuser) || (vuser->guest)
242 uid = conn->uid;
243 gid = conn->gid;
244 current_user.groups = conn->groups;
245 current_user.ngroups = conn->ngroups;
247 else
249 if (!vuser) {
250 DEBUG(2,("Invalid vuid used %d\n",vuid));
251 return(False);
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;
262 if (initial_uid == 0)
264 if (!become_gid(gid)) return(False);
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"));
273 #endif
275 if (!conn->admin_user && !become_uid(uid))
276 return(False);
279 current_user.cnum = cnum;
280 current_user.vuid = vuid;
282 DEBUG(5,("become_user uid=(%d,%d) gid=(%d,%d)\n",
283 getuid(),geteuid(),getgid(),getegid()));
285 return(True);
288 /****************************************************************************
289 unbecome the user of a connection number
290 ****************************************************************************/
291 BOOL unbecome_user(void )
293 if (current_user.cnum == -1)
294 return(False);
296 ChDir(OriginalDir);
298 if (initial_uid == 0)
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
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();
320 if (getegid() != initial_gid) {
321 DEBUG(0,("Warning: You appear to have a trapdoor gid system\n"));
322 initial_gid = getegid();
324 #endif
326 current_user.uid = initial_uid;
327 current_user.gid = initial_gid;
329 if (ChDir(OriginalDir) != 0)
330 DEBUG(0,("%s chdir(%s) failed in unbecome_user\n",
331 timestring(),OriginalDir));
333 DEBUG(5,("unbecome_user now uid=(%d,%d) gid=(%d,%d)\n",
334 getuid(),geteuid(),getgid(),getegid()));
336 current_user.cnum = -1;
337 current_user.vuid = UID_FIELD_INVALID;
339 return(True);
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)
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;
354 close(1);
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
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;
374 /* now create the file */
375 fd = open(outfile,flags,mode);
377 if (fd == -1) return False;
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;
385 close(fd);
387 return True;
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).
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.
398 if shared is not set then open the file with O_EXCL set
399 ****************************************************************************/
400 int smbrun(char *cmd,char *outfile,BOOL shared)
402 int fd,pid;
403 int uid = current_user.uid;
404 int gid = current_user.gid;
406 #ifndef HAVE_EXECL
407 int ret;
408 pstring syscmd;
409 char *path = lp_smbrun();
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))
417 DEBUG(0,("SMBRUN ERROR: Can't find %s. Installation problem?\n",path));
418 return(1);
421 slprintf(syscmd,sizeof(syscmd)-1,"%s %d %d \"(%s 2>&1) > %s\"",
422 path,uid,gid,cmd,
423 outfile?outfile:"/dev/null");
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 */
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;
440 return status;
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 */
448 /* point our stdout at the file we want output to go into */
449 if (outfile && !setup_stdout_file(outfile,shared)) {
450 exit(80);
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
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 */
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);
480 execl("/bin/sh","sh","-c",cmd,NULL);
482 /* not reached */
483 exit(82);
484 #endif
485 return 1;
488 static struct current_user current_user_saved;
489 static int become_root_depth;
490 static pstring become_root_dir;
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
498 Set save_dir if you also need to save/restore the CWD
499 ****************************************************************************/
500 void become_root(BOOL save_dir)
502 if (become_root_depth) {
503 DEBUG(0,("ERROR: become root depth is non zero\n"));
505 if (save_dir)
506 GetWd(become_root_dir);
508 current_user_saved = current_user;
509 become_root_depth = 1;
511 become_uid(0);
512 become_gid(0);
515 /****************************************************************************
516 When the privilaged operation is over call this
518 Set save_dir if you also need to save/restore the CWD
519 ****************************************************************************/
520 void unbecome_root(BOOL restore_dir)
522 if (become_root_depth != 1) {
523 DEBUG(0,("ERROR: unbecome root depth is %d\n",
524 become_root_depth));
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);
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");
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"));
546 #endif
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");
554 if (restore_dir)
555 ChDir(become_root_dir);
557 current_user = current_user_saved;
559 become_root_depth = 0;