/* * Copyright (C) 2013 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "common/cockpitframe.h" #include "common/cockpitjsonprint.h" #include "common/cockpitmemory.h" #include "client-certificate.h" #include "session-utils.h" #include #include #include #include #include static char *last_txt_msg = NULL; static char *last_err_msg = NULL; static char *conversation = NULL; /* 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 COCKPIT_KTAB PACKAGE_SYSCONF_DIR "/cockpit/krb5.keytab" /* exit status if cockpit-bridge binary does not exist */ #define EXIT_NOT_FOUND 127 static gss_cred_id_t creds = GSS_C_NO_CREDENTIAL; /* Environment variables to transfer */ static const char *env_names[] = { "G_DEBUG", "G_MESSAGES_DEBUG", "G_SLICE", "PATH", NULL }; /* Holds environment values to set in pam context */ static char *env_saved[sizeof (env_names) / sizeof (env_names)[0]] = { NULL, }; 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; /* For keeping track of messages returned by PAM */ char *err_msg = NULL; char *txt_msg = NULL; char *buf, **msgp; struct pam_response *resp; char *prompt = NULL; int success = 1; int i; /* Any messages from the last conversation pass? */ txt_msg = last_txt_msg; last_txt_msg = NULL; err_msg = last_err_msg; last_err_msg = NULL; resp = callocx (sizeof (struct pam_response), num_msg); 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 || msg[i]->msg_style == PAM_TEXT_INFO) { if (msg[i]->msg_style == PAM_ERROR_MSG) msgp = &err_msg; else msgp = &txt_msg; if (*msgp) { buf = *msgp; asprintfx (msgp, "%s\n%s", buf, msg[i]->msg); free (buf); } else { asprintfx (msgp, "%s", msg[i]->msg); } 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); write_control_end (); if (err_msg) { free (err_msg); err_msg = NULL; } if (txt_msg) { free (txt_msg); txt_msg = NULL; } char *authorization = read_authorize_response (msg[i]->msg); char *response = get_authorize_key (authorization, "response", true); char *prompt_resp = cockpit_authorize_parse_x_conversation (response, NULL); cockpit_memory_clear (response, -1); free (response); debug ("got prompt response"); if (prompt_resp) { resp[i].resp = prompt_resp; resp[i].resp_retcode = 0; prompt_resp = NULL; } else { success = 0; } 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; static struct passwd pwd_buf; static char pwd_string_buf[8192]; static char home_env_buf[8192]; 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 (pwd->pw_shell == NULL) { warnx ("user %s has no shell", name); 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); res = snprintf (home_env_buf, sizeof (home_env_buf), "HOME=%s", pwd->pw_dir); /* this really can't fail, as the buffer for the entire pwd is not larger, but make double sure */ assert (res < sizeof (home_env_buf)); pam_putenv (pamh, "XDG_SESSION_CLASS=user"); pam_putenv (pamh, "XDG_SESSION_TYPE=web"); pam_putenv (pamh, home_env_buf); 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; } __attribute__((__noreturn__)) static void exit_pam_init_problem (int result_code) { const char *problem = NULL; const char *message = NULL; assert (result_code != PAM_SUCCESS); debug ("writing PAM 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); exit_init_problem (problem, message); } 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; assert (rhost != NULL); assert (authorization != NULL); 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_pam_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); else btmp_log (user, rhost); free (user); if (password) { cockpit_memory_clear (password, strlen (password)); free (password); } /* Our exit code is a PAM code */ if (res != PAM_SUCCESS) exit_pam_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 = strndupx (local.value, local.length); /* user names are not allowed to contain \0 */ if (getpwnam (str)) { debug ("mapped gssapi name to local user '%s'", str); } else { debug ("ignoring non-existent 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 { assert (display.value != NULL); str = strndupx (display.value, display.length); /* display names are not allowed to contain \0 */ if (getpwnam (str)) { debug ("no local user mapping for gssapi name '%s'", str); } else { warnx ("non-existent 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 bool acquire_service_credentials (gss_OID mech_type, gss_cred_usage_t usage, gss_cred_id_t *cred) { /* custom credential store with our cockpit keytab */ gss_key_value_element_desc store_elements[] = { { .key = (usage == GSS_C_INITIATE) ? "client_keytab" : "keytab", .value = COCKPIT_KTAB, }, { .key = "ccache", .value = "MEMORY:", } }; const gss_key_value_set_desc cockpit_ktab_store = { .count = 2, .elements = store_elements }; OM_uint32 major, minor; debug ("acquiring cockpit service credentials"); major = gss_acquire_cred_from (&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, usage, (!getenv ("COCKPIT_TEST_KEEP_KTAB") && access (COCKPIT_KTAB, F_OK) == 0) ? &cockpit_ktab_store : NULL, cred, NULL, NULL); if (GSS_ERROR (major)) { const char *msg = gssapi_strerror (mech_type, major, minor); /* don't litter journal with error message if keytab was not set up, as that's expected */ /* older krb versions hide the interesting bits behind the generic GSS_S_FAILURE and an uncomparable * minor code, so do string comparison for these */ if (major != GSS_S_NO_CRED && !strstr (msg, "nonexistent or empty") && !strstr (msg, "No Kerberos credentials available")) warnx ("couldn't acquire server credentials: %o %s", major, msg); return false; } return true; } 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 *challenge; OM_uint32 flags = 0; char *str = NULL; OM_uint32 caps = 0; int res; assert (rhost != NULL); assert (authorization != NULL); res = PAM_AUTH_ERR; debug ("reading kerberos auth from cockpit-ws"); input.value = cockpit_authorize_parse_negotiate (authorization, &input.length); if (!acquire_service_credentials (mech_type, GSS_C_ACCEPT, &server)) { 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 challenge response * iterations ... so do that here. */ free (input.value); input.length = 0; debug ("need to continue gssapi negotiation"); char *authorize = read_authorize_response ("negotiate"); char *response = get_authorize_key (authorize, "response", false); cockpit_memory_clear (authorize, -1); free (authorize); 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) { btmp_log (str, rhost); 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_pam_init_problem (res); return pamh; } static int pam_conv_func_dummy (int num_msg, const struct pam_message **msg, struct pam_response **ret_resp, void *appdata_ptr) { /* we don't expect (nor can handle) any actual auth conversation here, but * PAM sometimes sends messages like "Creating home directory for USER" */ for (int i = 0; i < num_msg; ++i) debug ("got PAM conversation message, ignoring: %s", msg[i]->msg); return PAM_CONV_ERR; } static void create_s4u_ticket (const char *username) { OM_uint32 major, minor; gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL; gss_name_t impersonee = GSS_C_NO_NAME; debug ("Attempting to create an S4U ticket for user %s", username); if (!acquire_service_credentials (GSS_C_NO_OID, GSS_C_INITIATE, &server_cred)) goto out; gss_buffer_desc user_buf = { .length = strlen (username), .value = (char *) username }; major = gss_import_name (&minor, &user_buf, GSS_KRB5_NT_PRINCIPAL_NAME, &impersonee); if (GSS_ERROR (major)) { warnx ("Failed to import user name %s: %s", username, gssapi_strerror (GSS_C_NO_OID, major, minor)); goto out; } /* store credentials into global creds; they will be put into the session and cleaned up in main() */ major = gss_acquire_cred_impersonate_name (&minor, server_cred, impersonee, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_INITIATE, &creds, NULL, NULL); if (GSS_ERROR (major)) { warnx ("Failed to impersonate %s: %s", username, gssapi_strerror (GSS_C_NO_OID, major, minor)); goto out; } debug ("S4U ticket for user %s created successfully", username); out: if (server_cred != GSS_C_NO_CREDENTIAL) gss_release_cred (&minor, &server_cred); if (impersonee != GSS_C_NO_NAME) gss_release_name(&minor, &impersonee); } static pam_handle_t * perform_tlscert (const char *rhost, const char *authorization) { struct pam_conv conv = { pam_conv_func_dummy, }; pam_handle_t *pamh; int res; assert (rhost != NULL); assert (authorization != NULL); debug ("start tls-cert authentication for cockpit-ws %u", getppid ()); /* True, otherwise we wouldn't be here. */ assert (strncmp (authorization, "tls-cert ", 9) == 0); const char *client_certificate_filename = authorization + 9; char *username = cockpit_session_client_certificate_map_user (client_certificate_filename); assert (username != NULL); res = pam_start ("cockpit", username, &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 rhost"); res = open_session (pamh); create_s4u_ticket (username); free (username); /* Our exit code is a PAM code */ if (res != PAM_SUCCESS) exit_pam_init_problem (res); return pamh; } /* Return path of ccache file (including FILE: prefix), clean this up at session end */ static char * store_krb_credentials (gss_cred_id_t creds, uid_t uid, gid_t gid) { gss_key_value_set_desc store; struct gss_key_value_element_struct element; OM_uint32 major, minor; char *ccache; assert (creds != GSS_C_NO_CREDENTIAL); bool was_root = getuid() == 0; /* We want to do this before the fork(), so we need to temporarily * change our euid/egid */ if (setresgid (gid, gid, -1) != 0 || setresuid (uid, uid, -1) != 0) err (127, "Unable to temporarily drop permissions to store gss credentials"); assert (geteuid () == uid && getegid() == gid); /* The ccache path needs to be unique per cockpit session; as cockpit-session runs throughout the * lifetime of sessions, our pid is unique. We expect the cache to be cleaned up at the end, but * if not, and the PID gets recycled, this just overwrites the old obsolete one, which is good. */ asprintfx (&ccache, "FILE:/run/user/%u/cockpit-session-%u.ccache", uid, getpid ()); debug ("storing kerberos credentials in session: %s", ccache); store.count = 1; store.elements = &element; element.key = "ccache"; element.value = ccache; 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)); if (was_root && (setresuid (0, 0, 0) != 0 || setresgid (0, 0, 0) != 0)) err (127, "Unable to restore permissions after storing gss credentials"); return ccache; } static void release_krb_credentials (char *ccache) { /* strip off FILE: prefix for deleting */ assert (strncmp (ccache, "FILE:", 5) == 0); if (unlink (ccache + 5) != 0) warn ("couldn't clean up kerberos ticket cache %s", ccache); free (ccache); } static bool user_has_valid_login_shell (const char **envp) { /* >>> random.randint(0,127) * 71 * https://xkcd.com/221/ */ const char *argv[] = { pwd->pw_shell, "-c", "exit 71;", NULL }; assert (argv[0] != NULL); int devnull = open ("/dev/null", O_RDONLY); if (devnull < 0) err (EX, "couldn't open /dev/null"); const int remap_fds[] = { devnull, 2, -1 }; /* send stdout to stderr */ int wstatus; wstatus = spawn_and_wait (argv, envp, remap_fds, 3, pwd->pw_uid, pwd->pw_gid); debug ("user_has_valid_login_shell: exited with status %x", wstatus); close (devnull); if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 71) return true; warnx ("User %s shell (%s) does not understand 'exit 71', unsupported with Cockpit", pwd->pw_name, pwd->pw_shell); return false; } 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_SESSION_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 void pass_to_child (int signo) { if (child > 0) kill (child, signo); } static void on_authorize_timeout (int signo) { /* Can't use errx() here: https://man7.org/linux/man-pages/man7/signal-safety.7.html */ static const char msg[] = "timed out waiting for authorize response\n"; /* ignore write errors(-Wunused-result); if that fails, we can do absolutely nothing about it */ (void) !write (STDERR_FILENO, msg, sizeof (msg) - 1); _exit(EX); } int main (int argc, char **argv) { pam_handle_t *pamh = NULL; OM_uint32 minor; char *type = NULL; const char **env; char *ccache = NULL; int status; int res; int i; if (isatty (0)) errx (2, "this command is not meant to be run from the console"); /* COMPAT: argv[1] used to be used, but is now ignored */ if (argc != 1 && argc != 2) errx (2, "invalid arguments to cockpit-session"); /* the initial authorization response should not take forever, time-bound it to guard * against hacking/PID recycling attempts */ sigaction (SIGALRM, &(struct sigaction) { .sa_handler = on_authorize_timeout, .sa_flags = 0 }, NULL); alarm (60); /* like cockpit_ws_auth_response_timeout on the ws side */ program_name = basename (argv[0]); 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_SESSION_PATH, 1); if (setgid (0) != 0 || setuid (0) != 0) err (1, "couldn't switch permissions correctly"); } 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 */ char *authorization = read_authorize_response ("authorization"); char *remote_peer = get_authorize_key (authorization, "remote-peer", false); /* the functions below require a non-NULL rhost */ const char *rhost = remote_peer ?: ""; debug ("initial authorize response rhost: %s", rhost); char *response = get_authorize_key (authorization, "response", true); cockpit_memory_clear (authorization, -1); free (authorization); if (!cockpit_authorize_type (response, &type)) errx (EX, "invalid authorization header received"); /* stop time-bounding */ alarm (0); sigaction (SIGALRM, &(struct sigaction) { .sa_handler = SIG_DFL, .sa_flags = 0 }, NULL); if (strcmp (type, "basic") == 0) pamh = perform_basic (rhost, response); else if (strcmp (type, "negotiate") == 0) pamh = perform_gssapi (rhost, response); else if (strcmp (type, "tls-cert") == 0) pamh = perform_tlscert (rhost, response); cockpit_memory_clear (response, -1); free (response); 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]); if (want_session) /* no session → no login messages or XDG_RUNTIME_DIR → no memfd or session ccache */ { if (pam_putenv (pamh, "COCKPIT_LOGIN_MESSAGES_MEMFD=3") != PAM_SUCCESS) errx (EX, "Failed to set COCKPIT_LOGIN_MESSAGES_MEMFD=3 in PAM environment"); if (creds != GSS_C_NO_CREDENTIAL) { ccache = store_krb_credentials (creds, pwd->pw_uid, pwd->pw_gid); char *ccache_env = NULL; asprintfx (&ccache_env, "KRB5CCNAME=%s", ccache); if (pam_putenv (pamh, ccache_env) != PAM_SUCCESS) errx (EX, "Failed to set KRB5CCNAME in PAM environment"); free (ccache_env); } } env = (const char **) pam_getenvlist (pamh); if (env == NULL) errx (EX, "get pam environment failed"); const char *bridge_argv[] = { "cockpit-bridge", NULL }; 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); if (!user_has_valid_login_shell (env)) { const char *msg = "\n{\"command\":\"init\",\"version\":1,\"problem\":\"unsupported-shell\"}"; if (cockpit_frame_write (STDOUT_FILENO, (unsigned char *)msg, strlen (msg)) < 0) err (EX, "couldn't write init message"); exit_pam_init_problem (PAM_PERM_DENIED); } signal (SIGTERM, pass_to_child); signal (SIGINT, pass_to_child); signal (SIGQUIT, pass_to_child); signal (SIGHUP, pass_to_child); FILE *login_messages = cockpit_json_print_open_memfd ("cockpit login messages", 1); utmp_log (1, rhost, login_messages); int login_messages_fd = cockpit_json_print_finish_memfd (&login_messages); const int remap_fds[] = { -1, -1, -1, login_messages_fd }; status = spawn_and_wait (bridge_argv, env, remap_fds, 4, pwd->pw_uid, pwd->pw_gid); utmp_log (0, rhost, NULL); signal (SIGTERM, SIG_DFL); signal (SIGINT, SIG_DFL); signal (SIGQUIT, SIG_DFL); signal (SIGHUP, SIG_DFL); close (login_messages_fd); 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)); if (ccache) release_krb_credentials (ccache); } else { status = spawn_and_wait (bridge_argv, env, NULL, -1, pwd->pw_uid, pwd->pw_gid); } /* translate no-cockpit error for the UI, as the precise status cannot be propagated * through the session Unix socket */ if (WIFEXITED (status) && WEXITSTATUS (status) == EXIT_NOT_FOUND) { debug ("bridge exited with status %d, no-cockpit", status); const char *no_cockpit = "\n{\"command\":\"init\",\"version\":1,\"problem\":\"no-cockpit\",\"message\":\"cockpit-bridge not found\"}"; if (cockpit_frame_write (STDOUT_FILENO, (unsigned char *)no_cockpit, strlen (no_cockpit)) < 0) err (EX, "couldn't write init message"); } else { debug ("bridge exited with status %d", status); } pam_end (pamh, PAM_SUCCESS); rhost = NULL; free (remote_peer); remote_peer = NULL; 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 (EX); }