selinux/policycoreutils/run_init/run_init.c

440 lines
12 KiB
C

/************************************************************************
*
* run_init
*
* SYNOPSIS:
*
* This program allows a user to run an /etc/init.d script in the proper context.
*
* USAGE:
*
* run_init <script> <args>
*
* BUILD OPTIONS:
*
* option USE_PAM:
*
* Set the USE_PAM constant if you want to authenticate users via PAM.
* If USE_PAM is not set, users will be authenticated via direct
* access to the shadow password file.
*
* If you decide to use PAM must be told how to handle run_init. A
* good rule-of-thumb might be to tell PAM to handle run_init in the
* same way it handles su, except that you should remove the pam_rootok.so
* entry so that even root must re-authenticate to run the init scripts
* in the proper context.
*
* If you choose not to use PAM, make sure you have a shadow passwd file
* in /etc/shadow. You can use a simlink if your shadow passwd file
* lives in another directory. Example:
* su
* cd /etc
* ln -s /etc/auth/shadow shadow
*
* If you decide not to use PAM, you will also have to make run_init
* setuid root, so that it can read the shadow passwd file.
*
*
*************************************************************************/
#include <stdio.h>
#include <stdlib.h> /* for malloc(), realloc(), free() */
#include <pwd.h> /* for getpwuid() */
#include <sys/types.h> /* to make getuid() and getpwuid() happy */
#include <sys/wait.h> /* for wait() */
#include <sys/stat.h> /* for struct stat and friends */
#include <getopt.h> /* for getopt_long() form of getopt() */
#include <selinux/selinux.h>
#include <selinux/get_default_type.h>
#include <selinux/context.h> /* for context-mangling functions */
#include <fcntl.h>
#include <ctype.h>
#include <limits.h>
#ifdef USE_AUDIT
#include <libaudit.h>
#endif
#ifdef USE_NLS
#include <libintl.h>
#include <locale.h>
#define _(msgid) gettext (msgid)
#else
#define _(msgid) (msgid)
#endif
#ifndef PACKAGE
#define PACKAGE "policycoreutils" /* the name of this package lang translation */
#endif
/* USAGE_STRING describes the command-line args of this program. */
#define USAGE_STRING _("USAGE: run_init <script> <args ...>\n\
where: <script> is the name of the init script to run,\n\
<args ...> are the arguments to that script.")
#define CONTEXT_FILE "initrc_context"
#ifdef USE_PAM
/************************************************************************
*
* All PAM code goes in this section.
*
************************************************************************/
#include <unistd.h> /* for getuid(), exit(), getopt() */
#include <security/pam_appl.h> /* for PAM functions */
#include <security/pam_misc.h> /* for misc_conv PAM utility function */
#define SERVICE_NAME "run_init" /* the name of this program for PAM */
/* The file containing the context to run
* the scripts under. */
/* authenticate_via_pam()
*
* in: p_passwd_line - struct containing data from our user's line in
* the passwd file.
* out: nothing
* return: value condition
* ----- ---------
* 1 PAM thinks that the user authenticated themselves properly
* 0 otherwise
*
* This function uses PAM to authenticate the user running this
* program. This is the only function in this program that makes PAM
* calls.
*
*/
static int authenticate_via_pam(const struct passwd *p_passwd_line)
{
int result = 0; /* our result, set to 0 (not authenticated) by default */
pam_handle_t *pam_handle; /* opaque handle used by all PAM functions */
/* This is a jump table of functions for PAM to use when it wants to *
* communicate with the user. We'll be using misc_conv(), which is *
* provided for us via pam_misc.h. */
struct pam_conv pam_conversation = {
misc_conv,
NULL
};
/* Make `p_pam_handle' a valid PAM handle so we can use it when *
* calling PAM functions. */
if (PAM_SUCCESS != pam_start(SERVICE_NAME,
p_passwd_line->pw_name,
&pam_conversation, &pam_handle)) {
fprintf(stderr, _("failed to initialize PAM\n"));
exit(-1);
}
/* Ask PAM to authenticate the user running this program */
if (PAM_SUCCESS == pam_authenticate(pam_handle, 0)) {
result = 1; /* user authenticated OK! */
}
/* If we were successful, call pam_acct_mgmt() to reset the
* pam_tally failcount.
*/
if (result && (PAM_SUCCESS != pam_acct_mgmt(pam_handle, 0)) ) {
fprintf(stderr, _("failed to get account information\n"));
exit(-1);
}
/* We're done with PAM. Free `pam_handle'. */
pam_end(pam_handle, PAM_SUCCESS);
return (result);
} /* authenticate_via_pam() */
#else /* else !USE_PAM */
/************************************************************************
*
* All shadow passwd code goes in this section.
*
************************************************************************/
#include <unistd.h> /* for getuid(), exit(), crypt() */
#include <shadow.h> /* for shadow passwd functions */
#include <string.h> /* for strlen(), memset() */
/*
* crypt() may not be defined in unistd.h; see:
* http://man7.org/linux/man-pages/man3/crypt.3.html#NOTES
*/
#if !defined(_XOPEN_CRYPT) || _XOPEN_CRYPT == -1
#include <crypt.h>
#endif
#define PASSWORD_PROMPT _("Password:") /* prompt for getpass() */
/* authenticate_via_shadow_passwd()
*
* in: p_passwd_line - struct containing data from our user's line in
* the passwd file.
* out: nothing
* return: value condition
* ----- ---------
* 1 user authenticated themselves properly according to the
* shadow passwd file.
* 0 otherwise
*
* This function uses the shadow passwd file to authenticate the user running
* this program.
*
*/
static int authenticate_via_shadow_passwd(const struct passwd *p_passwd_line)
{
struct spwd *p_shadow_line; /* struct derived from shadow passwd file line */
char *unencrypted_password_s; /* unencrypted password input by user */
char *encrypted_password_s; /* user's password input after being crypt()ed */
/* Make `p_shadow_line' point to the data from the current user's *
* line in the shadow passwd file. */
setspent(); /* Begin access to the shadow passwd file. */
p_shadow_line = getspnam(p_passwd_line->pw_name);
endspent(); /* End access to the shadow passwd file. */
if (!(p_shadow_line)) {
fprintf(stderr,
_
("Cannot find your entry in the shadow passwd file.\n"));
exit(-1);
}
/* Ask user to input unencrypted password */
if (!(unencrypted_password_s = getpass(PASSWORD_PROMPT))) {
fprintf(stderr, _("getpass cannot open /dev/tty\n"));
exit(-1);
}
/* Use crypt() to encrypt user's input password. Clear the *
* unencrypted password as soon as we're done, so it is not *
* visible to memory snoopers. */
encrypted_password_s = crypt(unencrypted_password_s,
p_shadow_line->sp_pwdp);
memset(unencrypted_password_s, 0, strlen(unencrypted_password_s));
/* Return 1 (authenticated) iff the encrypted version of the user's *
* input password matches the encrypted password stored in the *
* shadow password file. */
return (!strcmp(encrypted_password_s, p_shadow_line->sp_pwdp));
} /* authenticate_via_shadow_passwd() */
#endif /* if/else USE_PAM */
/*
* authenticate_user()
*
* Authenticate the user.
*
* in: nothing
* out: nothing
* return: 0 When success
* -1 When failure
*/
static int authenticate_user(void)
{
#define INITLEN 255
struct passwd *p_passwd_line; /* struct derived from passwd file line */
uid_t uid;
/*
* Determine the Linux user identity to re-authenticate.
* If supported and set, use the login uid, as this should be more stable.
* Otherwise, use the real uid.
* The SELinux user identity is no longer used, as Linux users are now
* mapped to SELinux users via seusers and the SELinux user identity space
* is separate.
*/
#ifdef USE_AUDIT
uid = audit_getloginuid();
if (uid == (uid_t) - 1)
uid = getuid();
#else
uid = getuid();
#endif
p_passwd_line = getpwuid(uid);
if (!p_passwd_line) {
fprintf(stderr, "cannot find your entry in the passwd file.\n");
return (-1);
}
printf("Authenticating %s.\n", p_passwd_line->pw_name);
/*
* Re-authenticate the user running this program.
* This is just to help confirm user intent (vs. invocation by
* malicious software), not to authorize the operation (which is covered
* by policy). Trusted path mechanism would be preferred.
*/
#ifdef USE_PAM
if (!authenticate_via_pam(p_passwd_line)) {
#else /* !USE_PAM */
if (!authenticate_via_shadow_passwd(p_passwd_line)) {
#endif /* if/else USE_PAM */
fprintf(stderr, _("run_init: incorrect password for %s\n"),
p_passwd_line->pw_name);
return (-1);
}
/* If we reach here, then we have authenticated the user. */
#ifdef CANTSPELLGDB
printf("You are authenticated!\n");
#endif
return 0;
} /* authenticate_user() */
/*
* get_init_context()
*
* Get the CONTEXT associated with the context for the init scripts. *
*
* in: nothing
* out: The CONTEXT associated with the context.
* return: 0 on success, -1 on failure.
*/
static int get_init_context(char **context)
{
FILE *fp;
char buf[255], *bufp;
int buf_len;
char context_file[PATH_MAX];
snprintf(context_file, sizeof(context_file) - 1, "%s/%s",
selinux_contexts_path(), CONTEXT_FILE);
fp = fopen(context_file, "r");
if (!fp) {
fprintf(stderr, _("Could not open file %s\n"), context_file);
return -1;
}
while (1) { /* loop until we find a non-empty line */
if (!fgets(buf, sizeof buf, fp))
break;
buf_len = strlen(buf);
if (buf[buf_len - 1] == '\n')
buf[buf_len - 1] = 0;
bufp = buf;
while (*bufp && isspace(*bufp))
bufp++;
if (*bufp) {
*context = strdup(bufp);
if (!(*context))
goto out;
fclose(fp);
return 0;
}
}
out:
fclose(fp);
fprintf(stderr, _("No context in file %s\n"), context_file);
return -1;
} /* get_init_context() */
/*****************************************************************************
* main() *
*****************************************************************************/
int main(int argc, char *argv[])
{
extern char *optarg; /* used by getopt() for arg strings */
extern int opterr; /* controls getopt() error messages */
char *new_context; /* context for the init script context */
#ifdef USE_NLS
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
#endif
/* Verify that we are running on a flask-enabled kernel. */
if (!is_selinux_enabled()) {
fprintf(stderr,
_
("Sorry, run_init may be used only on a SELinux kernel.\n"));
exit(-1);
}
/*
* Step 1: Handle command-line arguments. The first argument is the
* name of the script to run. All other arguments are for the script
* itself, and will be passed directly to the script.
*/
if (argc < 2) {
fprintf(stderr, "%s\n", USAGE_STRING);
exit(-1);
}
/*
* Step 2: Authenticate the user.
*/
if (authenticate_user() != 0) {
fprintf(stderr, _("authentication failed.\n"));
exit(-1);
}
/*
* Step 3: Get the context for the script to be run in.
*/
if (get_init_context(&new_context) == 0) {
#ifdef CANTSPELLGDB
printf("context is %s\n", new_context);
#endif
} else {
exit(-1);
}
/*
* Step 4: Run the command in the correct context.
*/
if (chdir("/")) {
perror("chdir");
free(new_context);
exit(-1);
}
if (setexeccon(new_context) < 0) {
fprintf(stderr, _("Could not set exec context to %s.\n"),
new_context);
free(new_context);
exit(-1);
}
free(new_context);
if (access("/usr/sbin/open_init_pty", X_OK) != 0) {
if (execvp(argv[1], argv + 1)) {
perror("execvp");
exit(-1);
}
return 0;
}
/*
* Do not execvp the command directly from run_init; since it would run
* under with a pty under sysadm_devpts_t. Instead, we call open_init_tty,
* which transitions us into initrc_t, which then spawns a new
* process, that gets a pty with context initrc_devpts_t. Just
* execvp or using a exec(1) recycles pty's, and does not open a new
* one.
*/
if (execvp("/usr/sbin/open_init_pty", argv)) {
perror("execvp");
exit(-1);
}
return 0;
} /* main() */