/*
* This file is part of Cockpit.
*
* Copyright (C) 2013 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see .
*/
#include "config.h"
#include "common/cockpitauthorize.h"
#include "common/cockpitframe.h"
#include "common/cockpitmemory.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* This program opens a session for a given user and runs the bridge in
* it. It is used to manage localhost; for remote hosts sshd does
* this job.
*/
#define DEBUG_SESSION 0
#define EX 127
#define DEFAULT_PATH "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
static struct passwd *pwd;
static struct passwd pwd_buf;
static char pwd_string_buf[8192];
static pid_t child;
static int want_session = 1;
static char *auth_prefix = NULL;
static size_t auth_prefix_size = 0;
static char *auth_msg = NULL;
static size_t auth_msg_size = 0;
static FILE *authf = NULL;
static char *last_err_msg = NULL;
static char *last_txt_msg = NULL;
static char *conversation = NULL;
static gss_cred_id_t creds = GSS_C_NO_CREDENTIAL;
#if DEBUG_SESSION
#define debug(fmt, ...) (fprintf (stderr, "cockpit-session: " fmt "\n", ##__VA_ARGS__))
#else
#define debug(...)
#endif
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
#define GNUC_NORETURN __attribute__((__noreturn__))
#else
#define GNUC_NORETURN
#endif
static char *
read_authorize_response (const char *what)
{
const char *auth_response = ",\"response\":\"";
size_t auth_response_size = 13;
const char *auth_suffix = "\"}";
size_t auth_suffix_size = 2;
unsigned char *message;
ssize_t len;
debug ("reading %s authorize message", what);
len = cockpit_frame_read (STDIN_FILENO, &message);
if (len < 0)
err (EX, "couldn't read %s", what);
/*
* The authorize messages we receive always have an exact prefix and suffix:
*
* \n{"command":"authorize","cookie":"NNN","response":"...."}
*/
if (len <= auth_prefix_size + auth_response_size + auth_suffix_size ||
memcmp (message, auth_prefix, auth_prefix_size) != 0 ||
memcmp (message + auth_prefix_size, auth_response, auth_response_size) != 0 ||
memcmp (message + (len - auth_suffix_size), auth_suffix, auth_suffix_size) != 0)
{
errx (EX, "didn't receive expected \"authorize\" message");
}
len -= auth_prefix_size + auth_response_size + auth_suffix_size;
memmove (message, message + auth_prefix_size + auth_response_size, len);
message[len] = '\0';
return (char *)message;
}
static void
write_control_string (const char *field,
const char *str)
{
const unsigned char *at;
char buf[8];
if (!str)
return;
debug ("writing %s %s", field, str);
fprintf (authf, ",\"%s\":\"", field);
for (at = (const unsigned char *)str; *at; at++)
{
if (*at == '\\' || *at == '\"' || *at < 0x1f)
{
snprintf (buf, sizeof (buf), "\\u%04x", (int)*at);
fputs_unlocked (buf, authf);
}
else
{
fputc_unlocked (*at, authf);
}
}
fputc_unlocked ('\"', authf);
}
static void
write_control_bool (const char *field,
int val)
{
const char *str = val ? "true" : "false";
debug ("writing %s %s", field, str);
fprintf (authf, ",\"%s\":%s", field, str);
}
static void
write_authorize_begin (void)
{
assert (authf == NULL);
assert (auth_msg_size == 0);
assert (auth_msg == NULL);
debug ("writing auth challenge");
if (auth_prefix)
{
free (auth_prefix);
auth_prefix = NULL;
}
if (asprintf (&auth_prefix, "\n{\"command\":\"authorize\",\"cookie\":\"session%u%u\"",
(unsigned int)getpid(), (unsigned int)time (NULL)) < 0)
{
errx (EX, "out of memory allocating string");
}
auth_prefix_size = strlen (auth_prefix);
authf = open_memstream (&auth_msg, &auth_msg_size);
fprintf (authf, "%s", auth_prefix);
}
static void
write_control_end (void)
{
assert (authf != NULL);
fprintf (authf, "}\n");
fflush (authf);
fclose (authf);
assert (auth_msg_size > 0);
assert (auth_msg != NULL);
if (cockpit_frame_write (STDOUT_FILENO, (unsigned char *)auth_msg, auth_msg_size) < 0)
err (EX, "couldn't write auth request");
debug ("finished auth request");
free (auth_msg);
auth_msg = NULL;
authf = NULL;
auth_msg_size = 0;
}
GNUC_NORETURN static void
exit_init_problem (int result_code)
{
const char *problem = NULL;
const char *message = NULL;
char *payload = NULL;
assert (result_code != PAM_SUCCESS);
debug ("writing init problem %d", result_code);
if (result_code == PAM_AUTH_ERR || result_code == PAM_USER_UNKNOWN)
problem = "authentication-failed";
else if (result_code == PAM_PERM_DENIED)
problem = "access-denied";
else if (result_code == PAM_AUTHINFO_UNAVAIL)
problem = "authentication-unavailable";
else
problem = "internal-error";
if (last_err_msg)
message = last_err_msg;
else
message = pam_strerror (NULL, result_code);
if (asprintf (&payload, "\n{\"command\":\"init\",\"version\":1,\"problem\":\"%s\",\"message\":\"%s\"}",
problem, message) < 0)
errx (EX, "couldn't allocate memory for message");
if (cockpit_frame_write (STDOUT_FILENO, (unsigned char *)payload, strlen (payload)) < 0)
err (EX, "couldn't write init message");
free (payload);
exit (5);
}
static void
build_string (char **buf,
size_t *size,
const char *str,
size_t len)
{
if (*size == 0)
return;
if (len > *size - 1)
len = *size - 1;
memcpy (*buf, str, len);
(*buf)[len] = '\0';
*buf += len;
*size -= len;
}
static char *
dup_string (const char *str,
size_t len)
{
char *buf = malloc (len + 1);
if (!buf)
err (EX, "couldn't allocate memory for string");
memcpy (buf, str, len);
buf[len] = '\0';
return buf;
}
static const char *
gssapi_strerror (gss_OID mech_type,
OM_uint32 major_status,
OM_uint32 minor_status)
{
static char buffer[1024];
OM_uint32 major, minor;
OM_uint32 ctx;
gss_buffer_desc status;
char *buf;
size_t len;
int had_major;
int had_minor;
debug ("gssapi: major_status: %8.8x, minor_status: %8.8x",
major_status, minor_status);
buf = buffer;
len = sizeof (buffer);
buf[0] = '\0';
had_major = 0;
ctx = 0;
if (major_status != GSS_S_FAILURE || minor_status == 0)
{
for (;;)
{
major = gss_display_status (&minor, major_status, GSS_C_GSS_CODE,
GSS_C_NO_OID, &ctx, &status);
if (GSS_ERROR (major))
break;
if (had_major)
build_string (&buf, &len, ": ", 2);
had_major = 1;
build_string (&buf, &len, status.value, status.length);
gss_release_buffer (&minor, &status);
if (!ctx)
break;
}
}
ctx = 0;
had_minor = 0;
for (;;)
{
major = gss_display_status (&minor, minor_status, GSS_C_MECH_CODE,
mech_type, &ctx, &status);
if (GSS_ERROR (major))
break;
if (had_minor)
build_string (&buf, &len, ", ", 2);
else if (had_major)
build_string (&buf, &len, " (", 2);
had_minor = 1;
build_string (&buf, &len, status.value, status.length);
gss_release_buffer (&minor, &status);
if (!ctx)
break;
}
if (had_major && had_minor)
build_string (&buf, &len, ")", 1);
return buffer;
}
static int
pam_conv_func (int num_msg,
const struct pam_message **msg,
struct pam_response **ret_resp,
void *appdata_ptr)
{
char **password = (char **)appdata_ptr;
char *authorization = NULL;
char *prompt_resp = NULL;
char *err_msg = NULL;
char *txt_msg = NULL;
char *buf;
int ar;
struct pam_response *resp;
char *prompt = NULL;
int success = 1;
int i;
txt_msg = last_txt_msg;
last_txt_msg = NULL;
err_msg = last_err_msg;
last_err_msg = NULL;
resp = calloc (sizeof (struct pam_response), num_msg);
if (resp == NULL)
{
warnx ("couldn't allocate memory for pam response");
return PAM_BUF_ERR;
}
for (i = 0; i < num_msg; i++)
{
if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF &&
*password != NULL)
{
debug ("answered pam password prompt");
resp[i].resp = *password;
resp[i].resp_retcode = 0;
*password = NULL;
}
else if (msg[i]->msg_style == PAM_ERROR_MSG)
{
if (err_msg)
{
buf = err_msg;
ar = asprintf (&err_msg, "%s\n%s", buf, msg[i]->msg);
free (buf);
}
else
{
ar = asprintf (&err_msg, "%s", msg[i]->msg);
}
if (ar < 0)
errx (EX, "couldn't allocate memory for error variable");
warnx ("pam: %s", msg[i]->msg);
}
else if (msg[i]->msg_style == PAM_TEXT_INFO)
{
if (txt_msg)
{
buf = txt_msg;
ar = asprintf (&txt_msg, "%s\n%s", txt_msg, msg[i]->msg);
free (buf);
}
else
{
ar = asprintf (&txt_msg, "%s", msg[i]->msg);
}
if (ar < 0)
errx (EX, "couldn't allocate memory for text variable");
warnx ("pam: %s", msg[i]->msg);
}
else
{
debug ("prompt for more data");
write_authorize_begin ();
prompt = cockpit_authorize_build_x_conversation (msg[i]->msg, &conversation);
if (!prompt)
err (EX, "couldn't generate prompt");
write_control_string ("challenge", prompt);
free (prompt);
if (txt_msg)
write_control_string ("message", txt_msg);
if (err_msg)
write_control_string ("error", err_msg);
write_control_bool ("echo", msg[i]->msg_style == PAM_PROMPT_ECHO_OFF ? 0 : 1);
write_control_end ();
if (err_msg)
{
free (err_msg);
err_msg = NULL;
}
if (txt_msg)
{
free (txt_msg);
txt_msg = NULL;
}
authorization = read_authorize_response (msg[i]->msg);
prompt_resp = cockpit_authorize_parse_x_conversation (authorization, NULL);
debug ("got prompt response");
if (prompt_resp)
{
resp[i].resp = prompt_resp;
resp[i].resp_retcode = 0;
prompt_resp = NULL;
}
else
{
success = 0;
}
if (authorization)
cockpit_memory_clear (authorization, -1);
free (authorization);
}
}
if (!success)
{
for (i = 0; i < num_msg; i++)
free (resp[i].resp);
free (resp);
return PAM_CONV_ERR;
}
if (err_msg)
last_err_msg = err_msg;
if (txt_msg)
last_txt_msg = txt_msg;
*ret_resp = resp;
return PAM_SUCCESS;
}
static int
open_session (pam_handle_t *pamh)
{
const char *name;
int res;
int i;
name = NULL;
pwd = NULL;
res = pam_get_item (pamh, PAM_USER, (const void **)&name);
if (res != PAM_SUCCESS)
{
warnx ("couldn't load user from pam");
return res;
}
res = getpwnam_r (name, &pwd_buf, pwd_string_buf, sizeof (pwd_string_buf), &pwd);
if (pwd == NULL)
{
warnx ("couldn't load user info for: %s: %s", name,
res == 0 ? "not found" : strerror (res));
return PAM_SYSTEM_ERR;
}
/*
* If we're already running as the right user, and have authenticated
* then skip starting a new session. This is used when testing, or
* running as your own user.
*/
want_session = !(geteuid () != 0 &&
geteuid () == pwd->pw_uid &&
getuid () == pwd->pw_uid &&
getegid () == pwd->pw_gid &&
getgid () == pwd->pw_gid);
if (want_session)
{
debug ("checking access for %s", name);
res = pam_acct_mgmt (pamh, 0);
if (res == PAM_NEW_AUTHTOK_REQD)
{
warnx ("user account or password has expired: %s: %s", name, pam_strerror (pamh, res));
/*
* Certain PAM implementations return PAM_AUTHTOK_ERR if the users input does not
* match criteria. Let the conversation happen three times in that case.
*/
for (i = 0; i < 3; i++) {
res = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
if (res != PAM_SUCCESS)
warnx ("unable to change expired account or password: %s: %s", name, pam_strerror (pamh, res));
if (res != PAM_AUTHTOK_ERR)
break;
}
}
else if (res != PAM_SUCCESS)
{
warnx ("user account access failed: %d %s: %s", res, name, pam_strerror (pamh, res));
}
if (res != PAM_SUCCESS)
{
/* We change PAM_AUTH_ERR to PAM_PERM_DENIED so that we can
* distinguish between failures here and in *
* pam_authenticate.
*/
if (res == PAM_AUTH_ERR)
res = PAM_PERM_DENIED;
return res;
}
debug ("opening pam session for %s", name);
pam_putenv (pamh, "XDG_SESSION_CLASS=user");
res = pam_setcred (pamh, PAM_ESTABLISH_CRED);
if (res != PAM_SUCCESS)
{
warnx ("establishing credentials failed: %s: %s", name, pam_strerror (pamh, res));
return res;
}
res = pam_open_session (pamh, 0);
if (res != PAM_SUCCESS)
{
warnx ("couldn't open session: %s: %s", name, pam_strerror (pamh, res));
return res;
}
res = pam_setcred (pamh, PAM_REINITIALIZE_CRED);
if (res != PAM_SUCCESS)
{
warnx ("reinitializing credentials failed: %s: %s", name, pam_strerror (pamh, res));
return res;
}
}
return PAM_SUCCESS;
}
static pam_handle_t *
perform_basic (const char *rhost,
const char *authorization)
{
struct pam_conv conv = { pam_conv_func, };
pam_handle_t *pamh;
char *password = NULL;
char *user = NULL;
int res;
debug ("basic authentication");
/* The input should be a user:password */
password = cockpit_authorize_parse_basic (authorization, &user);
if (password == NULL)
{
debug ("bad basic auth input");
exit_init_problem (PAM_BUF_ERR);
}
conv.appdata_ptr = &password;
res = pam_start ("cockpit", user, &conv, &pamh);
if (res != PAM_SUCCESS)
errx (EX, "couldn't start pam: %s", pam_strerror (NULL, res));
if (pam_set_item (pamh, PAM_RHOST, rhost) != PAM_SUCCESS)
errx (EX, "couldn't setup pam");
debug ("authenticating");
res = pam_authenticate (pamh, 0);
if (res == PAM_SUCCESS)
res = open_session (pamh);
free (user);
if (password)
{
cockpit_memory_clear (password, strlen (password));
free (password);
}
/* Our exit code is a PAM code */
if (res != PAM_SUCCESS)
exit_init_problem (res);
return pamh;
}
static char *
map_gssapi_to_local (gss_name_t name,
gss_OID mech_type)
{
gss_buffer_desc local = GSS_C_EMPTY_BUFFER;
gss_buffer_desc display = GSS_C_EMPTY_BUFFER;
OM_uint32 major, minor;
char *str = NULL;
major = gss_localname (&minor, name, mech_type, &local);
if (major == GSS_S_COMPLETE)
{
minor = 0;
str = dup_string (local.value, local.length);
if (getpwnam (str))
{
debug ("mapped gssapi name to local user '%s'", str);
}
else
{
debug ("ignoring non-existant gssapi local user '%s'", str);
/* If the local user doesn't exist, pretend gss_localname() failed */
free (str);
str = NULL;
major = GSS_S_FAILURE;
minor = KRB5_NO_LOCALNAME;
}
}
/* Try a more pragmatic approach */
if (!str)
{
if (minor == (OM_uint32)KRB5_NO_LOCALNAME ||
minor == (OM_uint32)KRB5_LNAME_NOTRANS ||
minor == (OM_uint32)ENOENT)
{
major = gss_display_name (&minor, name, &display, NULL);
if (GSS_ERROR (major))
{
warnx ("couldn't get gssapi display name: %s", gssapi_strerror (mech_type, major, minor));
}
else
{
str = dup_string (display.value, display.length);
if (getpwnam (str))
{
debug ("no local user mapping for gssapi name '%s'", str);
}
else
{
warnx ("non-existant local user '%s'", str);
free (str);
str = NULL;
}
}
}
else
{
warnx ("couldn't map gssapi name to local user: %s", gssapi_strerror (mech_type, major, minor));
}
}
if (display.value)
gss_release_buffer (&minor, &display);
if (local.value)
gss_release_buffer (&minor, &local);
return str;
}
static pam_handle_t *
perform_gssapi (const char *rhost,
const char *authorization)
{
struct pam_conv conv = { pam_conv_func, };
OM_uint32 major, minor;
gss_cred_id_t client = GSS_C_NO_CREDENTIAL;
gss_cred_id_t server = GSS_C_NO_CREDENTIAL;
gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
gss_buffer_desc export = GSS_C_EMPTY_BUFFER;
gss_name_t name = GSS_C_NO_NAME;
gss_ctx_id_t context = GSS_C_NO_CONTEXT;
gss_OID mech_type = GSS_C_NO_OID;
pam_handle_t *pamh = NULL;
char *response = NULL;
char *challenge;
OM_uint32 flags = 0;
const char *msg;
char *str = NULL;
OM_uint32 caps = 0;
int res;
res = PAM_AUTH_ERR;
debug ("reading kerberos auth from cockpit-ws");
input.value = cockpit_authorize_parse_negotiate (authorization, &input.length);
debug ("acquiring server credentials");
major = gss_acquire_cred (&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE, GSS_C_NO_OID_SET,
GSS_C_ACCEPT, &server, NULL, NULL);
if (GSS_ERROR (major))
{
/* This is a routine error message, don't litter */
msg = gssapi_strerror (mech_type, major, minor);
if (input.length == 0 && !strstr (msg, "nonexistent or empty"))
warnx ("couldn't acquire server credentials: %s", msg);
res = PAM_AUTHINFO_UNAVAIL;
goto out;
}
for (;;)
{
debug ("gssapi negotiation");
if (client != GSS_C_NO_CREDENTIAL)
gss_release_cred (&minor, &client);
if (name != GSS_C_NO_NAME)
gss_release_name (&minor, &name);
if (output.value)
gss_release_buffer (&minor, &output);
if (input.length > 0)
{
major = gss_accept_sec_context (&minor, &context, server, &input,
GSS_C_NO_CHANNEL_BINDINGS, &name, &mech_type,
&output, &flags, &caps, &client);
}
else
{
debug ("initial gssapi negotiate output");
major = GSS_S_CONTINUE_NEEDED;
}
/* Our exit code is a PAM result code */
if (GSS_ERROR (major))
{
res = PAM_AUTH_ERR;
warnx ("gssapi auth failed: %s", gssapi_strerror (mech_type, major, minor));
goto out;
}
if ((major & GSS_S_CONTINUE_NEEDED) == 0)
break;
challenge = cockpit_authorize_build_negotiate (output.value, output.length);
if (!challenge)
errx (EX, "couldn't encode negotiate challenge");
write_authorize_begin ();
write_control_string ("challenge", challenge);
write_control_end ();
cockpit_memory_clear (challenge, -1);
free (challenge);
/*
* The GSSAPI mechanism can require multiple chanllenge response
* iterations ... so do that here.
*/
free (input.value);
input.length = 0;
debug ("need to continue gssapi negotiation");
response = read_authorize_response ("negotiate");
input.value = cockpit_authorize_parse_negotiate (response, &input.length);
if (response)
cockpit_memory_clear (response, -1);
free (response);
}
str = map_gssapi_to_local (name, mech_type);
if (!str)
goto out;
res = pam_start ("cockpit", str, &conv, &pamh);
if (res != PAM_SUCCESS)
errx (EX, "couldn't start pam: %s", pam_strerror (NULL, res));
if (pam_set_item (pamh, PAM_RHOST, rhost) != PAM_SUCCESS)
errx (EX, "couldn't setup pam");
res = open_session (pamh);
if (res != PAM_SUCCESS)
goto out;
/* The creds are used and cleaned up later */
creds = client;
out:
if (output.value)
gss_release_buffer (&minor, &output);
if (export.value)
gss_release_buffer (&minor, &export);
if (server != GSS_C_NO_CREDENTIAL)
gss_release_cred (&minor, &server);
if (name != GSS_C_NO_NAME)
gss_release_name (&minor, &name);
if (context != GSS_C_NO_CONTEXT)
gss_delete_sec_context (&minor, &context, GSS_C_NO_BUFFER);
free (input.value);
free (str);
if (res != PAM_SUCCESS)
exit_init_problem (res);
return pamh;
}
static void
utmp_log (int login,
const char *rhost)
{
char id[UT_LINESIZE + 1];
struct utmp ut;
struct timeval tv;
int pid;
pid = getpid ();
snprintf (id, UT_LINESIZE, "%d", pid);
assert (pwd != NULL);
utmpname (_PATH_UTMP);
setutent ();
memset (&ut, 0, sizeof(ut));
strncpy (ut.ut_id, id, sizeof (ut.ut_id));
ut.ut_id[sizeof (ut.ut_id) - 1] = 0;
ut.ut_line[0] = 0;
if (login)
{
strncpy (ut.ut_user, pwd->pw_name, sizeof(ut.ut_user));
ut.ut_user[sizeof (ut.ut_user) - 1] = 0;
strncpy (ut.ut_host, rhost, sizeof(ut.ut_host));
ut.ut_host[sizeof (ut.ut_host) - 1] = 0;
}
gettimeofday (&tv, NULL);
ut.ut_tv.tv_sec = tv.tv_sec;
ut.ut_tv.tv_usec = tv.tv_usec;
ut.ut_type = login ? LOGIN_PROCESS : DEAD_PROCESS;
ut.ut_pid = pid;
pututline (&ut);
endutent ();
updwtmp (_PATH_WTMP, &ut);
}
static int
closefd (void *data,
int fd)
{
int *from = data;
if (fd >= *from)
{
while (close (fd) < 0)
{
if (errno == EAGAIN || errno == EINTR)
continue;
if (errno == EBADF || errno == EINVAL)
break;
warnx ("couldn't close fd in bridge process: %m");
return -1;
}
}
return 0;
}
#ifndef HAVE_FDWALK
static int
fdwalk (int (*cb)(void *data, int fd),
void *data)
{
int open_max;
int fd;
int res = 0;
struct rlimit rl;
#ifdef __linux__
DIR *d;
if ((d = opendir ("/proc/self/fd"))) {
struct dirent *de;
while ((de = readdir (d))) {
long l;
char *e = NULL;
if (de->d_name[0] == '.')
continue;
errno = 0;
l = strtol (de->d_name, &e, 10);
if (errno != 0 || !e || *e)
continue;
fd = (int) l;
if ((long) fd != l)
continue;
if (fd == dirfd (d))
continue;
if ((res = cb (data, fd)) != 0)
break;
}
closedir (d);
return res;
}
/* If /proc is not mounted or not accessible we fall back to the old
* rlimit trick */
#endif
if (getrlimit (RLIMIT_NOFILE, &rl) == 0 && rl.rlim_max != RLIM_INFINITY)
open_max = rl.rlim_max;
else
open_max = sysconf (_SC_OPEN_MAX);
for (fd = 0; fd < open_max; fd++)
if ((res = cb (data, fd)) != 0)
break;
return res;
}
#endif /* HAVE_FDWALK */
static int
session (char **env)
{
char *argv[] = { "cockpit-bridge", NULL };
gss_key_value_set_desc store;
struct gss_key_value_element_struct element;
OM_uint32 major, minor;
krb5_context k5;
int res;
if (creds != GSS_C_NO_CREDENTIAL)
{
res = krb5_init_context (&k5);
if (res == 0)
{
store.count = 1;
store.elements = &element;
element.key = "ccache";
element.value = krb5_cc_default_name (k5);
debug ("storing kerberos credentials in session: %s", element.value);
major = gss_store_cred_into (&minor, creds, GSS_C_INITIATE, GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
if (GSS_ERROR (major))
warnx ("couldn't store gssapi credentials: %s", gssapi_strerror (GSS_C_NO_OID, major, minor));
krb5_free_context (k5);
}
else
{
warnx ("couldn't initialize kerberos context: %s", krb5_get_error_message (NULL, res));
}
}
debug ("executing bridge: %s", argv[0]);
if (env)
execvpe (argv[0], argv, env);
else
execvp (argv[0], argv);
warn ("can't exec %s", argv[0]);
return 127;
}
static int
fork_session (char **env)
{
int status;
int from;
fflush (stderr);
assert (pwd != NULL);
child = fork ();
if (child < 0)
{
warn ("can't fork");
return 1 << 8;
}
if (child == 0)
{
if (setgid (pwd->pw_gid) < 0)
{
warn ("setgid() failed");
_exit (42);
}
if (setuid (pwd->pw_uid) < 0)
{
warn ("setuid() failed");
_exit (42);
}
if (getuid() != geteuid() &&
getgid() != getegid())
{
warnx ("couldn't drop privileges");
_exit (42);
}
debug ("dropped privileges");
from = 3;
if (fdwalk (closefd, &from) < 0)
{
warnx ("couldn't close all file descirptors");
_exit (42);
}
_exit (session (env));
}
close (0);
close (1);
waitpid (child, &status, 0);
return status;
}
static void
pass_to_child (int signo)
{
if (child > 0)
kill (child, signo);
}
/* Environment variables to transfer */
static const char *env_names[] = {
"G_DEBUG",
"G_MESSAGES_DEBUG",
"G_SLICE",
"PATH",
"COCKPIT_REMOTE_PEER",
NULL
};
/* Holds environment values to set in pam context */
static char *env_saved[sizeof (env_names) / sizeof (env_names)[0]] = { NULL, };
static void
save_environment (void)
{
const char *value;
int i, j;
/* Force save our default path */
if (!getenv ("COCKPIT_TEST_KEEP_PATH"))
setenv ("PATH", DEFAULT_PATH, 1);
for (i = 0, j = 0; env_names[i] != NULL; i++)
{
value = getenv (env_names[i]);
if (value)
{
if (asprintf (env_saved + (j++), "%s=%s", env_names[i], value) < 0)
errx (42, "couldn't allocate environment");
}
}
env_saved[j] = NULL;
}
static const char *
get_environ_var (const char *name,
const char *defawlt)
{
return getenv (name) ? getenv (name) : defawlt;
}
static void
authorize_logger (const char *data)
{
warnx ("%s", data);
}
int
main (int argc,
char **argv)
{
pam_handle_t *pamh = NULL;
OM_uint32 minor;
const char *rhost;
char *authorization;
char *type = NULL;
char **env;
int status;
int res;
int i;
if (isatty (0))
errx (2, "this command is not meant to be run from the console");
/* argv[1] is ignored */
if (argc != 2)
errx (2, "invalid arguments to cockpit-session");
/* Cleanup the umask */
umask (077);
rhost = get_environ_var ("COCKPIT_REMOTE_PEER", "");
save_environment ();
/* When setuid root, make sure our group is also root */
if (geteuid () == 0)
{
/* Always clear the environment */
if (clearenv () != 0)
err (1, "couldn't clear environment");
/* set a minimal environment */
setenv ("PATH", DEFAULT_PATH, 1);
if (setgid (0) != 0 || setuid (0) != 0)
err (1, "couldn't switch permissions correctly");
}
signal (SIGALRM, SIG_DFL);
signal (SIGQUIT, SIG_DFL);
signal (SIGTSTP, SIG_IGN);
signal (SIGHUP, SIG_IGN);
signal (SIGPIPE, SIG_IGN);
cockpit_authorize_logger (authorize_logger, DEBUG_SESSION);
/* Request authorization header */
write_authorize_begin ();
write_control_string ("challenge", "*");
write_control_end ();
/* And get back the authorization header */
authorization = read_authorize_response ("authorization");
if (!cockpit_authorize_type (authorization, &type))
errx (EX, "invalid authorization header received");
if (strcmp (type, "basic") == 0)
pamh = perform_basic (rhost, authorization);
else if (strcmp (type, "negotiate") == 0)
pamh = perform_gssapi (rhost, authorization);
cockpit_memory_clear (authorization, -1);
free (authorization);
if (!pamh)
errx (2, "unrecognized authentication method: %s", type);
free (type);
for (i = 0; env_saved[i] != NULL; i++)
pam_putenv (pamh, env_saved[i]);
env = pam_getenvlist (pamh);
if (env == NULL)
errx (EX, "get pam environment failed");
if (want_session)
{
assert (pwd != NULL);
if (initgroups (pwd->pw_name, pwd->pw_gid) < 0)
err (EX, "%s: can't init groups", pwd->pw_name);
signal (SIGTERM, pass_to_child);
signal (SIGINT, pass_to_child);
signal (SIGQUIT, pass_to_child);
utmp_log (1, rhost);
status = fork_session (env);
utmp_log (0, rhost);
signal (SIGTERM, SIG_DFL);
signal (SIGINT, SIG_DFL);
signal (SIGQUIT, SIG_DFL);
res = pam_setcred (pamh, PAM_DELETE_CRED);
if (res != PAM_SUCCESS)
err (EX, "%s: couldn't delete creds: %s", pwd->pw_name, pam_strerror (pamh, res));
res = pam_close_session (pamh, 0);
if (res != PAM_SUCCESS)
err (EX, "%s: couldn't close session: %s", pwd->pw_name, pam_strerror (pamh, res));
}
else
{
status = session (env);
}
pam_end (pamh, PAM_SUCCESS);
free (last_err_msg);
last_err_msg = NULL;
free (last_txt_msg);
last_txt_msg = NULL;
free (conversation);
conversation = NULL;
if (creds != GSS_C_NO_CREDENTIAL)
gss_release_cred (&minor, &creds);
if (WIFEXITED(status))
exit (WEXITSTATUS(status));
else if (WIFSIGNALED(status))
raise (WTERMSIG(status));
else
exit (127);
}