592 lines
14 KiB
C
592 lines
14 KiB
C
/* rpasswd.c -- restricted `passwd' wrapper.
|
|
Copyright (C) 1996 Adam Solesby, Joshua Cowan
|
|
|
|
This program 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, or (at your option)
|
|
any later version.
|
|
|
|
This program 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 this program; if not, write to the Free Software Foundation,
|
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
|
|
|
|
/* This program is meant to be a wrapper for use with `sudo' and your
|
|
system's `passwd' program. It is *probably* secure, but there is no
|
|
warranty (see above). If you find errors or security holes, please
|
|
email me; please include a complete description of the problem in
|
|
your message in addition to any patches. */
|
|
|
|
/* This program currently assumes that the arguments given on the
|
|
command line are user names to pass to the `passwd' program; it loops
|
|
through the arguments calling `passwd' on each one. It might be
|
|
better to pass all remaining arguments after `--' to `passwd' (to
|
|
e.g., change the user's shell instead of the password by giving it
|
|
the `-s' option). */
|
|
|
|
/* Written by Adam Solesby <adam@shack.com>. */
|
|
/* Rewritten by Joshua Cowan <jcowan@hermit.reslife.okstate.edu>. */
|
|
|
|
/* Usage: rpasswd USERNAME...
|
|
Enforce password-changing guidelines.
|
|
|
|
--check[=file] check configuration information; if FILE is given,
|
|
use that instead of the standard configuration
|
|
file `./rpasswd.conf'
|
|
--help display this help and exit
|
|
--version output version information and exit
|
|
|
|
You may never change a superuser's password with this command.
|
|
Changing certain other users' passwords may also be forbidden; for
|
|
details of who's passwords may not be changed, try `rpasswd --check'. */
|
|
|
|
/* TODO:
|
|
|
|
- Make this more portable. It currently depends on several
|
|
GNU/Linux-specific features. */
|
|
|
|
#include <stdio.h>
|
|
#include <getopt.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <ctype.h>
|
|
#include <pwd.h>
|
|
#include <stdarg.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
/* This is the absolute path to the `passwd' program on your system. */
|
|
#define _PATH_PASSWD "/usr/bin/passwd"
|
|
|
|
/* This is the absolute path to the configuration file. */
|
|
#define _PATH_RPASSWD_CONF "/etc/rpasswd.conf"
|
|
|
|
/* Don't change the password of any user with a uid equal to or below
|
|
this number--no matter what the configuration file says. */
|
|
#define UID_PWD_CHANGE_FLOOR 100
|
|
|
|
/* Everything past this point should probably be left alone. */
|
|
|
|
/* These are the facility and priority (respectively) used by the syslog
|
|
functions. */
|
|
#define LOG_FACILITY LOG_AUTH
|
|
#define LOG_PRIORITY LOG_WARNING
|
|
|
|
/* The name this program was run with. */
|
|
char *program_name;
|
|
|
|
/* The version information for this program. */
|
|
char *version_string = "1.2";
|
|
|
|
/* If nonzero, display usage information and exit. */
|
|
static int show_help;
|
|
|
|
/* If nonzero, print the version on standard output then exit. */
|
|
static int show_version;
|
|
|
|
/* If nonzero, check the configuration file for errors and print the
|
|
list of restrictions on the standard output, then exit. */
|
|
static int check_only;
|
|
|
|
struct user_list
|
|
{
|
|
char *name;
|
|
struct user_list *next;
|
|
};
|
|
|
|
struct config_info
|
|
{
|
|
/* Don't change the password for any user with a uid less than or
|
|
equal to this number. */
|
|
uid_t minimum_uid;
|
|
|
|
/* Don't change the password for any user matching this list of user
|
|
names. */
|
|
struct user_list *inviolate_user_names;
|
|
};
|
|
|
|
static const struct option long_options[] =
|
|
{
|
|
{"check", optional_argument, 0, 10},
|
|
{"version", no_argument, &show_version, 1},
|
|
{"help", no_argument, &show_help, 1},
|
|
{0, 0, 0, 0}
|
|
};
|
|
|
|
static struct config_info *get_config_info ();
|
|
static int dump_config_info ();
|
|
static void *xmalloc ();
|
|
static void *xrealloc ();
|
|
static void xsyslog (int, const char *, ...);
|
|
static void dal_error (int, int, const char *, ...);
|
|
|
|
static void
|
|
usage (status)
|
|
int status;
|
|
{
|
|
if (status != 0)
|
|
fprintf (stderr, "Try `%s --help' for more information.\n",
|
|
program_name);
|
|
else
|
|
{
|
|
printf ("Usage: %s USERNAME...\n", program_name);
|
|
fputs ("\
|
|
Enforce password-changing guidelines.\n\
|
|
\n\
|
|
--check[=file] check configuration information; if FILE is given,\n\
|
|
use that instead of the standard configuration file\n\
|
|
`"_PATH_RPASSWD_CONF"'\n\
|
|
--help display this help and exit\n\
|
|
--version output version information and exit\n",
|
|
stdout);
|
|
|
|
printf ("\n\
|
|
You may never change a superuser's password with this command. Changing\n\
|
|
certain other users' passwords may also be forbidden; for details of\n\
|
|
who's passwords may not be changed, try `%s --check'.\n",
|
|
program_name);
|
|
}
|
|
|
|
exit (status);
|
|
}
|
|
|
|
int
|
|
main (argc, argv)
|
|
int argc;
|
|
char **argv;
|
|
{
|
|
char *executing_user_name;
|
|
char *config_file_name = _PATH_RPASSWD_CONF;
|
|
int opt;
|
|
struct config_info *config;
|
|
|
|
/* Setting values of global variables. */
|
|
program_name = argv[0];
|
|
|
|
while ((opt = getopt_long (argc, argv, "", long_options, 0))
|
|
!= EOF)
|
|
switch (opt)
|
|
{
|
|
case 0:
|
|
break;
|
|
|
|
case 10:
|
|
check_only = 1;
|
|
if (optarg)
|
|
config_file_name = optarg;
|
|
break;
|
|
|
|
default:
|
|
usage (1);
|
|
}
|
|
|
|
if (show_version)
|
|
{
|
|
printf ("rpasswd %s\n", version_string);
|
|
return 0;
|
|
}
|
|
|
|
if (show_help)
|
|
{
|
|
usage (0);
|
|
}
|
|
|
|
if (check_only)
|
|
{
|
|
dump_config_info (config_file_name);
|
|
exit (0);
|
|
}
|
|
|
|
if (optind >= argc)
|
|
{
|
|
fprintf (stderr, "%s: missing argument\n", program_name);
|
|
usage (1);
|
|
}
|
|
|
|
/* FIXME: does `sudo' set the real user id to the effective user id?
|
|
If so, this won't work as intended: We want to get the name of the
|
|
user who ran `sudo'. I am reluctant to use `getlogin' for obvious
|
|
reasons, but it may be better than nothing. Maybe someone who
|
|
actually has `sudo' installed can tell me if this works, or how to
|
|
fix it if it doesn't. --JC */
|
|
do
|
|
{
|
|
struct passwd *pwd;
|
|
uid_t uid = getuid ();
|
|
|
|
pwd = getpwuid (uid);
|
|
|
|
if (!pwd || !pwd->pw_name)
|
|
{
|
|
xsyslog (LOG_PRIORITY,
|
|
"Unknown user (uid #%d) attempted to change password for `%s'.",
|
|
uid, argv[optind]);
|
|
fprintf (stderr, "%s: you do not exist, go away\n",
|
|
program_name);
|
|
exit (1);
|
|
}
|
|
else
|
|
executing_user_name = pwd->pw_name;
|
|
}
|
|
while (0);
|
|
|
|
config = get_config_info (config_file_name);
|
|
|
|
for (; optind < argc; optind++)
|
|
{
|
|
int immutable_p = 0;
|
|
struct user_list *user_names = config->inviolate_user_names;
|
|
|
|
/* Make sure we weren't given an illegal user name. */
|
|
for (; user_names; user_names = user_names->next)
|
|
{
|
|
if (strcmp (argv[optind], user_names->name)
|
|
== 0)
|
|
{
|
|
immutable_p = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!immutable_p)
|
|
{
|
|
struct passwd *pwd;
|
|
|
|
pwd = getpwnam (argv[optind]);
|
|
|
|
if (!pwd)
|
|
{
|
|
fprintf (stderr, "%s: invalid user `%s'\n",
|
|
program_name, argv[optind]);
|
|
|
|
continue;
|
|
}
|
|
else if (pwd->pw_uid <= config->minimum_uid)
|
|
immutable_p = 1;
|
|
}
|
|
|
|
if (immutable_p)
|
|
{
|
|
xsyslog (LOG_PRIORITY,
|
|
"`%s' attempted to change password for `%s'.",
|
|
executing_user_name, argv[optind]);
|
|
fprintf (stderr,
|
|
"You are not allowed to change the password for `%s'.\n",
|
|
argv[optind]);
|
|
}
|
|
else
|
|
{
|
|
int pid, status;
|
|
|
|
pid = fork ();
|
|
switch (pid)
|
|
{
|
|
case -1:
|
|
dal_error (1, errno, "cannot fork");
|
|
|
|
case 0:
|
|
execl (_PATH_PASSWD, _PATH_PASSWD, "--", argv[optind], 0);
|
|
_exit (1);
|
|
|
|
default:
|
|
while (wait (&status) != pid)
|
|
;
|
|
|
|
if (status & 0xFFFF)
|
|
dal_error (1, EIO, "%s", _PATH_PASSWD);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
exit (0);
|
|
}
|
|
|
|
/* Get configuration information from FILE and return a pointer to a
|
|
`config_info' structure containing that information. It currently
|
|
does minimal checking of the validity of the information.
|
|
|
|
This function never returns NULL, even when the configuration file is
|
|
empty. If the configuration file doesn't exist, it just exits with a
|
|
failed exit status. */
|
|
|
|
static struct config_info *
|
|
get_config_info (file)
|
|
const char *const file;
|
|
{
|
|
FILE *config_file;
|
|
struct config_info *config;
|
|
char linebuf[BUFSIZ];
|
|
unsigned int lineno = 0;
|
|
|
|
config = (struct config_info *) xmalloc (sizeof (struct config_info));
|
|
config->minimum_uid = (uid_t) 0;
|
|
config->inviolate_user_names = 0;
|
|
|
|
config_file = fopen (file, "r");
|
|
if (!config_file)
|
|
dal_error (1, errno, "%s", file);
|
|
|
|
if (fseek (config_file, 0L, SEEK_SET))
|
|
dal_error (1, errno, "%s", file);
|
|
|
|
while (fgets (linebuf, BUFSIZ, config_file))
|
|
{
|
|
int len, i, uid_found = 0;
|
|
|
|
lineno++;
|
|
|
|
len = strlen (linebuf);
|
|
|
|
/* Chomp any whitespace off the end of the line. */
|
|
while (isspace (linebuf[len - 1]))
|
|
linebuf[--len] = '\0';
|
|
|
|
/* If this line is empty or a comment, skip it and go to the next. */
|
|
if (len == 0 || *linebuf == '#')
|
|
continue;
|
|
|
|
for (i = 0; i < len; i++)
|
|
if (!isalnum (linebuf[i])
|
|
&& linebuf[i] != '.'
|
|
&& linebuf[i] != '-'
|
|
&& linebuf[i] != '_')
|
|
{
|
|
dal_error (1, 0, "%s:%u: invalid user name `%s'",
|
|
file, lineno, linebuf);
|
|
}
|
|
|
|
/* Only accept positive integers as candidates for `minimum_uid'. */
|
|
for (i = 0; i < len; i++)
|
|
if (!isdigit (linebuf[i]))
|
|
break;
|
|
|
|
if (!uid_found && i == len)
|
|
{
|
|
unsigned long num;
|
|
|
|
errno = 0;
|
|
num = strtoul (linebuf, 0, 10);
|
|
config->minimum_uid = (uid_t) num;
|
|
|
|
if (errno || config->minimum_uid != num)
|
|
dal_error (1, 0, "%s:%u: `%s' out of range",
|
|
file, lineno, linebuf);
|
|
|
|
uid_found = 1;
|
|
}
|
|
else
|
|
{
|
|
struct user_list *tail = config->inviolate_user_names;
|
|
struct user_list *user_names = 0;
|
|
|
|
/* This could be more efficient, but makes the list of users
|
|
printed out with the `--check' switch easier to read. */
|
|
|
|
for (; tail; tail = tail->next)
|
|
{
|
|
if (strcmp (linebuf, tail->name) == 0)
|
|
break;
|
|
|
|
user_names = tail;
|
|
}
|
|
|
|
if (!tail)
|
|
{
|
|
tail = user_names;
|
|
|
|
user_names = xmalloc (sizeof (struct user_list));
|
|
user_names->name = strcpy (xmalloc (len + 1), linebuf);
|
|
user_names->next = 0;
|
|
|
|
if (!config->inviolate_user_names)
|
|
config->inviolate_user_names = user_names;
|
|
else
|
|
tail->next = user_names;
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose (config_file);
|
|
|
|
if (config->minimum_uid < UID_PWD_CHANGE_FLOOR)
|
|
config->minimum_uid = UID_PWD_CHANGE_FLOOR;
|
|
|
|
return config;
|
|
}
|
|
|
|
/* Dump the configuration info contained in FILE to the standard output. */
|
|
|
|
static int
|
|
dump_config_info (file)
|
|
char *file;
|
|
{
|
|
struct config_info *config;
|
|
|
|
config = get_config_info (file);
|
|
|
|
printf ("\
|
|
The lowest uid who's password may be changed is number %d. Changing
|
|
the following users' passwords is also forbidden:\n",
|
|
config->minimum_uid + 1);
|
|
|
|
if (!config->inviolate_user_names)
|
|
{
|
|
printf ("\n (no users listed in configuration file `%s')\n",
|
|
file);
|
|
}
|
|
else
|
|
{
|
|
int column;
|
|
struct user_list *user_names = config->inviolate_user_names;
|
|
|
|
for (column = 73; user_names; user_names = user_names->next)
|
|
{
|
|
int name_len = strlen (user_names->name);
|
|
|
|
if (user_names->next)
|
|
name_len++;
|
|
|
|
column += name_len;
|
|
|
|
if (column > 72)
|
|
{
|
|
fputs ("\n ", stdout);
|
|
column = name_len + 2;
|
|
}
|
|
else if (column - name_len > 0)
|
|
{
|
|
fputc (' ', stdout);
|
|
column++;
|
|
}
|
|
|
|
fputs (user_names->name, stdout);
|
|
|
|
if (user_names->next)
|
|
fputc (',', stdout);
|
|
}
|
|
|
|
fputc ('\n', stdout);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void *
|
|
xmalloc (n)
|
|
size_t n;
|
|
{
|
|
void *ptr;
|
|
|
|
ptr = malloc (n);
|
|
|
|
if (!ptr)
|
|
{
|
|
fprintf (stderr, "%s: Memory exhausted\n", program_name);
|
|
exit (1);
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
static void *
|
|
xrealloc (ptr, n)
|
|
void *ptr;
|
|
size_t n;
|
|
{
|
|
ptr = realloc (ptr, n);
|
|
|
|
if (!ptr)
|
|
{
|
|
fprintf (stderr, "%s: Memory exhausted\n", program_name);
|
|
exit (1);
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
static void
|
|
xsyslog (int priority, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
static int logfd_opened = 0;
|
|
|
|
if (!logfd_opened)
|
|
{
|
|
openlog (program_name, LOG_PID, LOG_FACILITY);
|
|
logfd_opened = 1;
|
|
}
|
|
|
|
va_start (args, format);
|
|
vsyslog (priority, format, args);
|
|
va_end (args);
|
|
}
|
|
|
|
/* Format and display MESSAGE on the standard error and send it to the
|
|
system logger. If ERRNUM is not 0, append the system error message
|
|
corresponding to ERRNUM to the output. If STATUS is not 0, exit with
|
|
an exit status of STATUS. */
|
|
|
|
static void
|
|
dal_error (int status, int errnum, const char *message, ...)
|
|
{
|
|
va_list args;
|
|
size_t bufsize;
|
|
char *formatted_message;
|
|
|
|
fflush (stdout);
|
|
|
|
bufsize = strlen (message) * 2;
|
|
formatted_message = (char *) xmalloc (bufsize);
|
|
|
|
va_start (args, message);
|
|
|
|
while (1)
|
|
{
|
|
int printed;
|
|
printed = vsnprintf (formatted_message, bufsize, message, args);
|
|
|
|
if ((size_t) printed < bufsize)
|
|
break;
|
|
|
|
bufsize *= 2;
|
|
formatted_message = xrealloc (formatted_message, bufsize);
|
|
}
|
|
|
|
va_end (args);
|
|
|
|
if (errnum)
|
|
{
|
|
char *error_message = strerror (errnum);
|
|
|
|
formatted_message
|
|
= xrealloc (formatted_message,
|
|
(strlen (formatted_message)
|
|
+ strlen (error_message)
|
|
+ 3));
|
|
|
|
strcat (formatted_message, ": ");
|
|
strcat (formatted_message, error_message);
|
|
}
|
|
|
|
fprintf (stderr, "%s: %s\n", program_name, formatted_message);
|
|
|
|
xsyslog (LOG_PRIORITY, "%s", formatted_message);
|
|
|
|
free (formatted_message);
|
|
fflush (stderr);
|
|
|
|
if (status)
|
|
{
|
|
closelog ();
|
|
exit (status);
|
|
}
|
|
}
|