Re: Are there good reasons for running radiusd as user 'root'?

Maurice Makaay <[email protected]> Wed, 12 Nov 2003 12:00:39 +0100
Newsgroups gmane.comp.gnu.radius.bugs
Organization InterNLnet BV
Message-ID <[email protected]>
Hi,

> You only have to run it as root if you plan to use Auth-Type = System
> and/or Auth-Type = PAM. Otherwise it is OK to run radiusd with usual user
> privileges, provided that the logging directory is writable for that
> user or group (the default /var/log obviously isn't).
...
> That is reasonable. I'll surely add the feature.

I created a patch which adds a new keyword "exec-user" to the configuration
file's vocabulary. By setting the exec-user, one can determine under which
uid the server should be run. Things that are in the patch:

* Cleanup of some old references to exec-program-group, which were still
  in the texinfo files.

* A new type of configuration CFG_USER in config.y. This type handles
  users that are specified using either uids or usernames. This will
  fill a new type of struct "user_info" with some basic user information
  (the username, the uid and the gid of a user; a complete passwd struct
  would contain way more info than needed).

* A new droppriv.c file, which handles privilege dropping. The current
  implementation for privilege dropping (for running external programs)
  is only changing the euid. I think this is bad policy because this
  way the external program can simply change its uid back to the root uid.
  Setting the exec-program-user probably has a safety-reason, so let's
  be as safe as we can. If a user really wants to keep the ruid "root",
  he can simply set run-user and exec-program-user to "root" in the
  radius server's config and set the euid in the external program himself.
  The drop_privileges() function takes a pointer to a "user_info" struct
  as its argument.

* The new way of setting the run-user/exec-program-user has has fixed 
  one of the FIXME's in the source of radiusd/exec.c:
    Check user/group
    FIXME: This should be checked *once* after re-reading the
    configuration

* config.syntax contains information on run-user and some related 
  remarks were added to exec-program-user.

* Several checks are added to warn the user for:
  - the use of non-existant uids/usernames (this will cause the server
    to exit).
  - mixing two differing non-root users for run-user and exec-program-user,
    in which case the exec-program-user will not work (because only root
    can switch uid).
  - using Auth-Type System and/or PAM, for which root-access is needed.
  - not being able to find the default user 'daemon' on the system, in
    which case the program will revert to the user 'root' for
    exec-program-user.


There are two files attached: a diff for changes that are made to the
already existing files in CVS and a copy of the new file droppriv.c.


There's a FIXME of my own in radiusd.c:
    /* Now, try to drop our privileges... FIXME: exit on failure?*/
    drop_privileges(&run_user);
I think it would be best to have the server aborting in case the privilege 
dropping fails, but I don't know what your opinion on this is..


Regards,

-- Maurice Makaay

_______________________________________________
Bug-gnu-radius mailing list
[email protected]
http://mail.gnu.org/mailman/listinfo/bug-gnu-radius
CVS-droppriv.c (text/plain, 2.4 KB)
/* This file is part of GNU Radius.
   Copyright (C) 2000,2001,2002,2003 Free Software Foundation, Inc.

   Written by Sergey Poznyakoff

   GNU Radius is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   GNU Radius is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GNU Radius; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <sys/types.h>
#include <unistd.h>
#include <radiusd.h>

int
drop_privileges(struct user_info *user)
{
	if (user->uid == 0 && user->gid == 0) return 0;

	if (user->gid != 0 && setgid(user->gid)) {
		radlog(L_ERR|L_PERROR,
		       _("setgid(%d) failed"), user->gid);
		return -1;
	}

	if (user->uid != 0) {
#if defined(HAVE_SETUID)
		if (setuid(user->uid)) {
		    radlog(L_ERR|L_PERROR,
			   _("setuid(%d) failed (ruid=%d, euid=%d)"), 
			   user->uid, getuid(), geteuid());
		    return -1;
		}
#elif defined(HAVE_SETREUID)
		if (setreuid(user->uid, user->uid)) {
		    radlog(L_ERR|L_PERROR,
			   _("setreuid(%d,%d) failed (ruid=%d, euid=%d)"), 
			   user->uid, user->uid, getuid(), geteuid());
		    return -1;
		}
#elif defined(HAVE_SETRUID) && defined(HAVE_SETEUID)
		if (seteuid(user->uid) || setruid(user->uid)) {
		    radlog(L_ERR|L_PERROR,
			   _("seteuid/setruid(%d) failed (ruid=%d, euid=%d)"), 
			   user->uid, getuid(), geteuid());
		    return -1;
		}
#elif defined(HAVE_SETEUID)
		if (seteuid(user->uid)) {
		    radlog(L_ERR|L_PERROR,
			   _("seteuid(%d) failed (ruid=%d, euid=%d)"), 
			   user->uid, getuid(), geteuid());
		    return -1;
		}
		radlog(L_WARN,
		       _("Incomplete drop of privileges to user %s (uid %d): "
			 "only the effective uid is set"), 
                       user->username, user->uid);
#else
# error "*** NO WAY TO SET REAL AND EFFECTIVE UID IN drop_privileges() ***"
#endif
	}

        radlog(L_INFO, 
	       _("Dropped privileges: user=%s gid=%d ruid=%d euid=%d"),
	       user->username, getgid(), getuid(), geteuid());

	return 0;
}
CVS-patch-dropprivileges (text/plain, 17.3 KB)
Index: configure.ac
===================================================================
RCS file: /cvsroot/radius/radius/configure.ac,v
retrieving revision 1.56
diff -u -r1.56 configure.ac
--- configure.ac	8 Nov 2003 15:47:33 -0000	1.56
+++ configure.ac	12 Nov 2003 10:01:03 -0000
@@ -235,7 +235,8 @@
 AC_CHECK_FUNCS(setsid gethostname gettimeofday \
  mkdir mktime select socket strdup strtol lockf \
  setlocale bzero \
- inet_ntoa inet_aton setvbuf seteuid setreuid getdtablesize sigaction) 
+ inet_ntoa inet_aton setvbuf setuid setruid seteuid setreuid \
+ getdtablesize sigaction) 
 AC_FUNC_OBSTACK
 
 AC_CHECK_DECLS([strncasecmp, strtok_r, localtime_r, asprintf, vasprintf,
Index: doc/texinfo/attributes.texi
===================================================================
RCS file: /cvsroot/radius/radius/doc/texinfo/attributes.texi,v
retrieving revision 1.26
diff -u -r1.26 attributes.texi
--- doc/texinfo/attributes.texi	8 Nov 2003 09:48:08 -0000	1.26
+++ doc/texinfo/attributes.texi	12 Nov 2003 10:01:03 -0000
@@ -827,8 +827,8 @@
 
 Before the execution of the program, @command{radiusd} switches to the
 uid and gid of the user @code{daemon} and the group @code{daemon}. You can
-override these defaults by setting variables @code{exec-program-user}
-and @code{exec-program-group} in configuration file to proper values
+override these defaults by setting the variable @code{exec-program-user}
+in the configuration file to a proper value.
 (@pxref{option,, The option statement}).
 
 The accounting program must exit with status 0 to indicate a successful
@@ -1105,8 +1105,8 @@
 
 Before the execution of the program, @command{radiusd} switches to the
 uid and gid of the user @code{daemon} and the group @code{daemon}. You can
-override these defaults by setting variables @code{exec-program-user}
-and @code{exec-program-group} in configuration file to proper values
+override these defaults by setting the variable @code{exec-program-user}
+in the configuration file to a proper value.
 @ref{option,, The option statement}.
 
 The daemon does not wait for the process to terminate.
Index: elisp/radconf-mode.el
===================================================================
RCS file: /cvsroot/radius/radius/elisp/radconf-mode.el,v
retrieving revision 1.14
diff -u -r1.14 radconf-mode.el
--- elisp/radconf-mode.el	8 Nov 2003 09:48:08 -0000	1.14
+++ elisp/radconf-mode.el	12 Nov 2003 10:01:03 -0000
@@ -171,6 +171,7 @@
 		process-idle-timeout
 		master-read-timeout
 		master-write-timeout
+		run-user
                 exec-program-user
                 log-dir
                 acct-dir
Index: examples/config.syntax
===================================================================
RCS file: /cvsroot/radius/radius/examples/config.syntax,v
retrieving revision 1.19
diff -u -r1.19 config.syntax
--- examples/config.syntax	7 Nov 2003 14:47:00 -0000	1.19
+++ examples/config.syntax	12 Nov 2003 10:01:03 -0000
@@ -25,10 +25,29 @@
 	master-read-timeout 0;
 	master-write-timeout 0;
 
+	# The following specifies the uid under which the radius server
+	# should be run. The corresponding gid is determined from the 
+	# user's passwd entry. The default is root (but we advice you
+	# to use another use whenever possible). Both the username and
+	# the numerical uid of a user may be specified.
+	# Remarks: 
+	# If Auth-Type System or PAM is used, you will have to run your 
+	# radius server as root, because these types need root access.
+	# If you you run your server as a non-root user, make sure the
+	# logging and accounting directories are writable for that user
+	# and the raddb files are readable.
+	run-user "root";
+
 	# The following specifies the uid under which external processes
 	# (those triggered by Exec-Program and Exec-Program-Wait 
 	# attributes) should be run. The corresponding gid is determined
-	# from the user's passwd entry. Default is daemon.
+	# from the user's passwd entry. Default is daemon. Both the username
+	# and the numerical uid of a user may be specified.
+	# Remarks: 
+	# This feature will not work if the option run-user is set to a
+	# non-root user and the exec-program-user differs from the run-user. 
+	# If the run-user is set and the exec-program-user is not, then the 
+	# exec-program-user will automatically be the run-user.
 	exec-program-user "daemon";
 
 	# Specify the logging directory (-l command line option)
Index: include/cfg.h
===================================================================
RCS file: /cvsroot/radius/radius/include/cfg.h,v
retrieving revision 1.5
diff -u -r1.5 cfg.h
--- include/cfg.h	8 Nov 2003 09:48:09 -0000	1.5
+++ include/cfg.h	12 Nov 2003 10:01:03 -0000
@@ -41,6 +41,7 @@
 #define CFG_PORT    5
 #define CFG_CHAR    6
 #define CFG_HOST    7
+#define CFG_USER    8
 
 typedef struct {
         int type;
@@ -94,6 +95,8 @@
 int cfg_get_network(int argc, cfg_value_t *argv,
 		    void *block_data, void *handler_data);
 int cfg_get_port(int argc, cfg_value_t *argv,
+		 void *block_data, void *handler_data);
+int cfg_get_user(int argc, cfg_value_t *argv,
 		 void *block_data, void *handler_data);
 
 int cfg_read(char *fname, struct cfg_stmt *syntax, void *data);
Index: include/radius.h
===================================================================
RCS file: /cvsroot/radius/radius/include/radius.h,v
retrieving revision 1.67
diff -u -r1.67 radius.h
--- include/radius.h	8 Nov 2003 09:48:09 -0000	1.67
+++ include/radius.h	12 Nov 2003 10:01:03 -0000
@@ -411,6 +411,9 @@
 /* radpaths.c */
 void radpath_init();
 
+/* radck.c */
+int check_root_authtypes();
+
 /* users.y */
 typedef int (*register_rule_fp) (void *, LOCUS *, char *,
 				 VALUE_PAIR *, VALUE_PAIR *);
Index: include/radiusd.h
===================================================================
RCS file: /cvsroot/radius/radius/include/radiusd.h,v
retrieving revision 1.117
diff -u -r1.117 radiusd.h
--- include/radiusd.h	8 Nov 2003 09:48:09 -0000	1.117
+++ include/radiusd.h	12 Nov 2003 10:01:03 -0000
@@ -150,6 +150,16 @@
 				      checks */
 } User_symbol;
 
+/*
+ * Representation of a system user, to drop privileges to.
+ */
+struct user_info {
+	char *username;
+	int   uid;
+	int   gid;
+	int   is_default;
+};
+
 #define SNMP_RO 1
 #define SNMP_RW 2
 
@@ -284,7 +294,8 @@
 extern unsigned process_timeout;
 extern unsigned radiusd_write_timeout;
 extern unsigned radiusd_read_timeout;
-extern char *exec_user;
+extern struct user_info run_user;
+extern struct user_info exec_user;
 extern UINT4 expiration_seconds;
 extern UINT4 warning_seconds;
 extern int use_dbm;
@@ -384,6 +395,8 @@
 void radiusd_register_input_fd(char *name, int fd, void *data);
 void radiusd_close_channel(int fd);
 
+#define DEFAULT_EXEC_USER "daemon"
+
 /* exec.c */
 int radius_exec_program(char *, RADIUS_REQ *, VALUE_PAIR **, int);
 void filter_cleanup(pid_t pid, int status);
@@ -391,6 +404,9 @@
 int filter_acct(char *name, RADIUS_REQ *req);
 int filters_stmt_term(int finish, void *block_data, void *handler_data);
 extern struct cfg_stmt filters_stmt[];
+
+/* droppriv.c */
+int drop_privileges(struct user_info *);
 
 /* scheme.c */
 void scheme_main();
Index: raddb/config
===================================================================
RCS file: /cvsroot/radius/radius/raddb/config,v
retrieving revision 1.11
diff -u -r1.11 config
--- raddb/config	19 Jun 2003 12:39:42 -0000	1.11
+++ raddb/config	12 Nov 2003 10:01:04 -0000
@@ -6,6 +6,7 @@
 	# Uncomment and edit these if you need to:
 #	log-dir "/var/log";
 #	acct-dir "/var/acct";
+#	run-user "root";
 	resolve no;
 };
 
Index: radiusd/Makefile.am
===================================================================
RCS file: /cvsroot/radius/radius/radiusd/Makefile.am,v
retrieving revision 1.43
diff -u -r1.43 Makefile.am
--- radiusd/Makefile.am	8 Nov 2003 09:48:10 -0000	1.43
+++ radiusd/Makefile.am	12 Nov 2003 10:01:04 -0000
@@ -21,6 +21,7 @@
  checkrad.c\
  config.c\
  debugmod.c\
+ droppriv.c\
  exec.c\
  files.c\
  forward.c\
Index: radiusd/config.y
===================================================================
RCS file: /cvsroot/radius/radius/radiusd/config.y,v
retrieving revision 1.65
diff -u -r1.65 config.y
--- radiusd/config.y	8 Nov 2003 09:48:10 -0000	1.65
+++ radiusd/config.y	12 Nov 2003 10:01:04 -0000
@@ -618,6 +618,7 @@
         struct servent *s;
         UINT4 ipaddr;
         cfg_value_t value;
+	struct passwd *pwd;
 
 	value = *arg;
         switch (type) {
@@ -679,6 +680,44 @@
                 }
 		break;
 		
+	case CFG_USER:
+		pwd = NULL;
+		switch (value.type) {
+		case CFG_INTEGER:
+			pwd = getpwuid(value.v.number);
+		        if (pwd == NULL)
+				radlog(L_ERR, 
+				       _("%s:%d: no such uid: %d - EXITING"),
+                                       cfg_filename, cfg_line_num,
+                                       value.v.number);
+			break;
+		case CFG_STRING:
+			pwd = getpwnam(value.v.string);
+		        if (pwd == NULL)
+				radlog(L_ERR, 
+				       _("%s:%d: no such user: %s - EXITING"),
+                                       cfg_filename, cfg_line_num,
+                                       value.v.string);
+			break;
+                default:
+			radlog(L_CRIT, 
+			       _("%s:%d: illegal user specification - EXITING"),
+			       cfg_filename, cfg_line_num);
+                        break;
+                }
+
+		if (pwd != NULL) {
+		    struct user_info *user = base;
+		    if (user->username != NULL) efree(user->username);
+		    user->username = estrdup(pwd->pw_name); 
+		    user->uid = pwd->pw_uid;
+		    user->gid = pwd->pw_gid;
+		    user->is_default = 0;
+                }
+		else
+		    exit(1);
+		return 0;
+		break;
         }
         
         if (type != value.type) {
@@ -706,7 +745,7 @@
 	case CFG_NETWORK:
 		*(cfg_network_t *) base = value.v.network;
 		break;
-		
+
         default:
                 radlog(L_CRIT,
                        _("INTERNAL ERROR at %s:%d: unknown datatype %d"),
@@ -829,6 +868,14 @@
 {
 	_check_argc(argc, 1);
 	return _get_value(&argv[1], CFG_PORT, handler_data);
+}
+
+int
+cfg_get_user(int argc, cfg_value_t *argv, void *block_data,
+	       void *handler_data)
+{
+	_check_argc(argc, 1);
+	return _get_value(&argv[1], CFG_USER, handler_data);
 }
 
 int
Index: radiusd/exec.c
===================================================================
RCS file: /cvsroot/radius/radius/radiusd/exec.c,v
retrieving revision 1.59
diff -u -r1.59 exec.c
--- radiusd/exec.c	8 Nov 2003 09:48:10 -0000	1.59
+++ radiusd/exec.c	12 Nov 2003 10:01:04 -0000
@@ -36,7 +36,6 @@
 #include <signal.h>
 #include <errno.h>
 #include <sys/wait.h>
-#include <pwd.h>
 #include <grp.h>
 #include <string.h>
 #include <syslog.h>
@@ -46,35 +45,6 @@
 #include <symtab.h>
 #include <rewrite.h>
 
-int
-radius_change_uid(struct passwd *pwd)
-{
-	if (pwd->pw_gid != 0 && setgid(pwd->pw_gid)) {
-		radlog(L_ERR|L_PERROR,
-		       _("setgid(%d) failed"), pwd->pw_gid);
-		return -1;
-	}
-	if (pwd->pw_uid != 0) {
-#if defined(HAVE_SETEUID)
-		if (seteuid(pwd->pw_uid)) {
-			radlog(L_ERR|L_PERROR,
-			       _("seteuid(%d) failed (ruid=%d, euid=%d)"),
-			       pwd->pw_uid, getuid(), geteuid());
-			return -1;
-		}
-#elif defined(HAVE_SETREUID)
-		if (setreuid(0, pwd->pw_uid)) {
-			radlog(L_ERR|L_PERROR,
-			       _("setreuid(0,%d) failed (ruid=%d, euid=%d)"),
-			       pwd->pw_uid, getuid(), geteuid());
-			return -1;
-		}
-#else
-# error "*** NO WAY TO SET EFFECTIVE UID IN radius_change_uid() ***"
-#endif
-	}
-	return 0;
-}
 
 /* Execute a program on successful authentication.
    Return 0 if exec_wait == 0.
@@ -90,7 +60,6 @@
         FILE *fp;
         int line_num;
         char buffer[RAD_BUFFER_SIZE];
-        struct passwd *pwd;
 	pid_t pid;
 	int status;
 	RETSIGTYPE (*oldsig)();
@@ -102,17 +71,6 @@
                 return -1;
         }
 
-        /* Check user/group
-           FIXME: This should be checked *once* after re-reading the
-           configuration */
-        pwd = getpwnam(exec_user);
-        if (!pwd) {
-                radlog(L_ERR,
-    _("radius_exec_program(): won't execute, no such user: %s"),
-                       exec_user);
-                return -1;
-        }
-
         if (exec_wait) {
                 if (pipe(p) != 0) {
                         radlog(L_ERR|L_PERROR, _("couldn't open pipe"));
@@ -151,7 +109,7 @@
 
                 chdir("/tmp");
 
-                if (radius_change_uid(pwd))
+                if (drop_privileges(&exec_user))
 			exit(2);
 		
                 execvp(argv[0], argv);
@@ -227,17 +185,8 @@
 {
 	pid_t  pid;
 	int    rightp[2], leftp[2];
-	struct passwd *pwd;
 	int i;
 
-        pwd = getpwnam(exec_user);
-        if (!pwd) {
-                radlog(L_ERR,
-		       _("won't execute, no such user: %s"),
-                       exec_user);
-                return -1;
-        }
-
 	pipe(leftp);
 	pipe(rightp);
     
@@ -268,7 +217,7 @@
 		for (i = getmaxfd(); i > 2; i--)
 			close(i);
 
-		if (radius_change_uid(pwd))
+		if (drop_privileges(&exec_user))
 			exit(2);
 		execvp(argv[0], argv);
 		
Index: radiusd/radck.c
===================================================================
RCS file: /cvsroot/radius/radius/radiusd/radck.c,v
retrieving revision 1.22
diff -u -r1.22 radck.c
--- radiusd/radck.c	8 Nov 2003 09:48:10 -0000	1.22
+++ radiusd/radck.c	12 Nov 2003 10:01:04 -0000
@@ -462,3 +462,38 @@
                 rowi = (unsigned *) ((char *) rowi + rowsize);
         }
 }
+
+/* Check if there are Auth-Types used which need root access. */
+int
+root_authtype_checker(void *closure, User_symbol *sym)
+{
+        VALUE_PAIR *vp;
+
+        for (vp = sym->check; vp; vp = vp->next)
+        {
+		if (vp->attribute == DA_AUTH_TYPE &&
+		    (vp->avp_lvalue == DV_AUTH_TYPE_SYSTEM ||
+		     vp->avp_lvalue == DV_AUTH_TYPE_PAM)) {
+			(*(int *)closure)++;
+		}
+        }
+
+	return 0;
+}
+
+int
+check_root_authtypes()
+{
+	int fail_count = 0;
+
+	if (run_user.uid == 0) return 0; 
+
+	symtab_iterate(user_tab, root_authtype_checker, &fail_count);
+	if (fail_count) 
+	    radlog(L_WARN, 
+                   _("Root-only Auth-Type (System or PAM) used with "
+		     "non-root run-user %s: authentication might fail"),
+		   run_user.username);
+	return fail_count;
+}
+
Index: radiusd/radiusd.c
===================================================================
RCS file: /cvsroot/radius/radius/radiusd/radiusd.c,v
retrieving revision 1.148
diff -u -r1.148 radiusd.c
--- radiusd/radiusd.c	6 Nov 2003 14:33:23 -0000	1.148
+++ radiusd/radiusd.c	12 Nov 2003 10:01:04 -0000
@@ -30,6 +30,7 @@
 #include <signal.h>
 #include <errno.h>
 #include <sys/wait.h>
+#include <pwd.h>
 
 #include <radiusd.h>
 #include <radargp.h>
@@ -132,7 +133,8 @@
 unsigned process_timeout = PROCESS_TIMEOUT;
 unsigned radiusd_write_timeout = RADIUSD_WRITE_TIMEOUT;
 unsigned radiusd_read_timeout = RADIUSD_READ_TIMEOUT;
-char *exec_user = NULL;
+struct user_info run_user;
+struct user_info exec_user;
 
 UINT4 warning_seconds;
 int use_guile;
@@ -273,8 +275,27 @@
 void
 set_config_defaults()
 {
-        exec_user  = estrdup("daemon");
+	struct passwd *pwd = getpwnam("daemon");
+	if (pwd != NULL) {
+	    	exec_user.username = estrdup(DEFAULT_EXEC_USER);
+	    	exec_user.uid = pwd->pw_uid;
+	    	exec_user.gid = pwd->pw_gid;
+	} else { 
+		radlog(L_WARN, 
+		       _("User '%s' not found: reverting to "
+			 "'root' as the default exec-program-user!"),
+		       DEFAULT_EXEC_USER);
+	    	exec_user.username = estrdup("root");
+	    	exec_user.uid = exec_user.gid = 0;
+	}
+	exec_user.is_default = 1;
+
+	run_user.username = estrdup("root");
+	run_user.uid = run_user.gid = 0;
+	run_user.is_default = 1;
+
         username_valid_chars = estrdup(".-_!@#$%^&\\/");
+
         message_text[MSG_ACCOUNT_CLOSED] =
                 estrdup(_("Sorry, your account is currently closed\n"));
         message_text[MSG_PASSWORD_EXPIRED] =
@@ -372,6 +393,35 @@
 			radlog(L_ALERT,
 			       _("Radiusd is not listening on any port. Trying to continue anyway..."));
 	}
+
+        /* Is the program not running as root? */
+	if (run_user.uid) {
+		/* Check if the exec_user is the default exec_user. If yes, 
+		   then copy the run_user to the exec_user. If not, then 
+		   check if the exec_user differs from the run_user. In 
+		   that case things won't work correctly (after startup 
+		   we'll be non-root, so we won't be able to switch uid/gid 
+		   anymore). */
+		if (exec_user.is_default) {
+			exec_user.username = estrdup(run_user.username);
+			exec_user.uid = run_user.uid;
+			exec_user.gid = exec_user.gid;
+		}	
+		else if (run_user.uid != exec_user.uid) {
+			radlog(L_ALERT,
+			       _("Both the run-user and exec-program-user "
+				 "are set, but they differ. This will not "
+				 "work correctly in case external programs "
+				 "are used!"));
+		}
+
+		/* Check if Auth-Type System or PAM is ever used. For this
+		   Auth-Type, the program should run as root to function. */
+ 		check_root_authtypes();
+	}
+
+	/* Now, try to drop our privileges... FIXME: exit on failure?*/
+	drop_privileges(&run_user);
 }
 
 void
@@ -490,7 +540,7 @@
 {
 	return radiusd_pid == getpid();
 }
-	
+
 
 /* ****************************** Main function **************************** */
 
@@ -1221,7 +1271,9 @@
 	  cfg_get_integer, &radiusd_read_timeout, NULL, NULL },
 	{ "master-write-timeout", CS_STMT, NULL,
 	  cfg_get_integer, &radiusd_write_timeout, NULL, NULL },
-	{ "exec-program-user", CS_STMT, NULL, cfg_get_string, &exec_user,
+	{ "run-user", CS_STMT, NULL, cfg_get_user, &run_user,
+	  NULL, NULL },
+	{ "exec-program-user", CS_STMT, NULL, cfg_get_user, &exec_user,
 	  NULL, NULL },
 	{ "log-dir", CS_STMT, NULL, cfg_get_string, &radlog_dir,
 	  NULL, NULL },