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 #include #include -#include #include #include #include @@ -46,35 +45,6 @@ #include #include -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 #include #include +#include #include #include @@ -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("address@hidden&\\/"); + 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 },