diff --git a/crypto/openssh/auth-pam.c b/crypto/openssh/auth-pam.c index f95f6abbcbe0..df08dbd99a9d 100644 --- a/crypto/openssh/auth-pam.c +++ b/crypto/openssh/auth-pam.c @@ -1,1413 +1,1413 @@ /*- * Copyright (c) 2002 Networks Associates Technology, Inc. * All rights reserved. * * This software was developed for the FreeBSD Project by ThinkSec AS and * NAI Labs, the Security Research Division of Network Associates, Inc. * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the * DARPA CHATS research program. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 2003,2004 Damien Miller * Copyright (c) 2003,2004 Darren Tucker * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* Based on FreeBSD: src/crypto/openssh/auth2-pam-freebsd.c,v 1.11 2003/03/31 13:48:18 des */ #include "includes.h" #include #include #include #include #include #include #include #include #include #ifdef USE_PAM #if defined(HAVE_SECURITY_PAM_APPL_H) #include #elif defined (HAVE_PAM_PAM_APPL_H) #include #endif /* OpenGroup RFC86.0 and XSSO specify no "const" on arguments */ #ifdef PAM_SUN_CODEBASE # define sshpam_const /* Solaris, HP-UX, SunOS */ #else # define sshpam_const const /* LinuxPAM, OpenPAM, AIX */ #endif /* Ambiguity in spec: is it an array of pointers or a pointer to an array? */ #ifdef PAM_SUN_CODEBASE # define PAM_MSG_MEMBER(msg, n, member) ((*(msg))[(n)].member) #else # define PAM_MSG_MEMBER(msg, n, member) ((msg)[(n)]->member) #endif #include "xmalloc.h" #include "sshbuf.h" #include "ssherr.h" #include "hostfile.h" #include "auth.h" #include "auth-pam.h" #include "canohost.h" #include "log.h" #include "msg.h" #include "packet.h" #include "misc.h" #include "servconf.h" #include "ssh2.h" #include "auth-options.h" #include "misc.h" #ifdef GSSAPI #include "ssh-gss.h" #endif #include "monitor_wrap.h" #include "srclimit.h" #include "blacklist_client.h" extern ServerOptions options; extern struct sshbuf *loginmsg; extern u_int utmp_len; /* so we don't silently change behaviour */ #ifdef USE_POSIX_THREADS # error "USE_POSIX_THREADS replaced by UNSUPPORTED_POSIX_THREADS_HACK" #endif /* * Formerly known as USE_POSIX_THREADS, using this is completely unsupported * and generally a bad idea. Use at own risk and do not expect support if * this breaks. */ #ifdef UNSUPPORTED_POSIX_THREADS_HACK #include /* * Avoid namespace clash when *not* using pthreads for systems *with* * pthreads, which unconditionally define pthread_t via sys/types.h * (e.g. Linux) */ typedef pthread_t sp_pthread_t; #else typedef pid_t sp_pthread_t; #define pthread_exit fake_pthread_exit #define pthread_create fake_pthread_create #define pthread_cancel fake_pthread_cancel #define pthread_join fake_pthread_join #endif struct pam_ctxt { sp_pthread_t pam_thread; int pam_psock; int pam_csock; int pam_done; }; static void sshpam_free_ctx(void *); static struct pam_ctxt *cleanup_ctxt; #ifndef UNSUPPORTED_POSIX_THREADS_HACK /* * Simulate threads with processes. */ static int sshpam_thread_status = -1; static sshsig_t sshpam_oldsig; static void sshpam_sigchld_handler(int sig) { ssh_signal(SIGCHLD, SIG_DFL); if (cleanup_ctxt == NULL) return; /* handler called after PAM cleanup, shouldn't happen */ if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, WNOHANG) <= 0) { /* PAM thread has not exitted, privsep slave must have */ kill(cleanup_ctxt->pam_thread, SIGTERM); while (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, 0) == -1) { if (errno == EINTR) continue; return; } } if (sshpam_thread_status == -1) return; if (WIFSIGNALED(sshpam_thread_status)) { if (signal_is_crash(WTERMSIG(sshpam_thread_status))) _exit(EXIT_CHILD_CRASH); } else if (!WIFEXITED(sshpam_thread_status)) _exit(EXIT_CHILD_CRASH); } /* ARGSUSED */ static void pthread_exit(void *value) { _exit(0); } /* ARGSUSED */ static int pthread_create(sp_pthread_t *thread, const void *attr, void *(*thread_start)(void *), void *arg) { pid_t pid; struct pam_ctxt *ctx = arg; sshpam_thread_status = -1; switch ((pid = fork())) { case -1: error("fork(): %s", strerror(errno)); return errno; case 0: close(ctx->pam_psock); ctx->pam_psock = -1; thread_start(arg); _exit(1); default: *thread = pid; close(ctx->pam_csock); ctx->pam_csock = -1; sshpam_oldsig = ssh_signal(SIGCHLD, sshpam_sigchld_handler); return (0); } } static int pthread_cancel(sp_pthread_t thread) { ssh_signal(SIGCHLD, sshpam_oldsig); return (kill(thread, SIGTERM)); } /* ARGSUSED */ static int pthread_join(sp_pthread_t thread, void **value) { int status; if (sshpam_thread_status != -1) return (sshpam_thread_status); ssh_signal(SIGCHLD, sshpam_oldsig); while (waitpid(thread, &status, 0) == -1) { if (errno == EINTR) continue; fatal("%s: waitpid: %s", __func__, strerror(errno)); } return (status); } #endif static pam_handle_t *sshpam_handle = NULL; static int sshpam_err = 0; static int sshpam_authenticated = 0; static int sshpam_session_open = 0; static int sshpam_cred_established = 0; static int sshpam_account_status = -1; static int sshpam_maxtries_reached = 0; static char **sshpam_env = NULL; static Authctxt *sshpam_authctxt = NULL; static const char *sshpam_password = NULL; static char *sshpam_rhost = NULL; static char *sshpam_laddr = NULL; /* Some PAM implementations don't implement this */ #ifndef HAVE_PAM_GETENVLIST static char ** pam_getenvlist(pam_handle_t *pamh) { /* * XXX - If necessary, we can still support environment passing * for platforms without pam_getenvlist by searching for known * env vars (e.g. KRB5CCNAME) from the PAM environment. */ return NULL; } #endif #ifndef HAVE_PAM_PUTENV static int pam_putenv(pam_handle_t *pamh, const char *name_value) { return PAM_SUCCESS; } #endif /* HAVE_PAM_PUTENV */ /* * Some platforms, notably Solaris, do not enforce password complexity * rules during pam_chauthtok() if the real uid of the calling process * is 0, on the assumption that it's being called by "passwd" run by root. * This wraps pam_chauthtok and sets/restore the real uid so PAM will do * the right thing. */ #ifdef SSHPAM_CHAUTHTOK_NEEDS_RUID static int sshpam_chauthtok_ruid(pam_handle_t *pamh, int flags) { int result; if (sshpam_authctxt == NULL) fatal("PAM: sshpam_authctxt not initialized"); if (setreuid(sshpam_authctxt->pw->pw_uid, -1) == -1) fatal("%s: setreuid failed: %s", __func__, strerror(errno)); result = pam_chauthtok(pamh, flags); if (setreuid(0, -1) == -1) fatal("%s: setreuid failed: %s", __func__, strerror(errno)); return result; } # define pam_chauthtok(a,b) (sshpam_chauthtok_ruid((a), (b))) #endif static void sshpam_password_change_required(int reqd) { extern struct sshauthopt *auth_opts; static int saved_port, saved_agent, saved_x11; debug3("%s %d", __func__, reqd); if (sshpam_authctxt == NULL) fatal("%s: PAM authctxt not initialized", __func__); sshpam_authctxt->force_pwchange = reqd; if (reqd) { saved_port = auth_opts->permit_port_forwarding_flag; saved_agent = auth_opts->permit_agent_forwarding_flag; saved_x11 = auth_opts->permit_x11_forwarding_flag; auth_opts->permit_port_forwarding_flag = 0; auth_opts->permit_agent_forwarding_flag = 0; auth_opts->permit_x11_forwarding_flag = 0; } else { if (saved_port) auth_opts->permit_port_forwarding_flag = saved_port; if (saved_agent) auth_opts->permit_agent_forwarding_flag = saved_agent; if (saved_x11) auth_opts->permit_x11_forwarding_flag = saved_x11; } } /* Import regular and PAM environment from subprocess */ static void import_environments(struct sshbuf *b) { char *env; u_int n, i, num_env; int r; debug3("PAM: %s entering", __func__); #ifndef UNSUPPORTED_POSIX_THREADS_HACK /* Import variables set by do_pam_account */ if ((r = sshbuf_get_u32(b, &n)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (n > INT_MAX) fatal("%s: invalid PAM account status %u", __func__, n); sshpam_account_status = (int)n; if ((r = sshbuf_get_u32(b, &n)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); sshpam_password_change_required(n != 0); /* Import environment from subprocess */ if ((r = sshbuf_get_u32(b, &num_env)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (num_env > 1024) { fatal_f("received %u environment variables, expected <= 1024", num_env); } sshpam_env = xcalloc(num_env + 1, sizeof(*sshpam_env)); debug3("PAM: num env strings %u", num_env); for(i = 0; i < num_env; i++) { if ((r = sshbuf_get_cstring(b, &(sshpam_env[i]), NULL)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); } sshpam_env[num_env] = NULL; /* Import PAM environment from subprocess */ if ((r = sshbuf_get_u32(b, &num_env)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (num_env > 1024) { fatal_f("received %u PAM env variables, expected <= 1024", num_env); } debug("PAM: num PAM env strings %u", num_env); for (i = 0; i < num_env; i++) { if ((r = sshbuf_get_cstring(b, &env, NULL)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); /* Errors are not fatal here */ if ((r = pam_putenv(sshpam_handle, env)) != PAM_SUCCESS) { error("PAM: pam_putenv: %s", pam_strerror(sshpam_handle, r)); } /* * XXX this possibly leaks env because it is not documented * what pam_putenv() does with it. Does it copy it? Does it * take ownweship? We don't know, so it's safest just to leak. */ } #endif } /* * Conversation function for authentication thread. */ static int sshpam_thread_conv(int n, sshpam_const struct pam_message **msg, struct pam_response **resp, void *data) { struct sshbuf *buffer; struct pam_ctxt *ctxt; struct pam_response *reply; int r, i; u_char status; debug3("PAM: %s entering, %d messages", __func__, n); *resp = NULL; if (data == NULL) { error("PAM: conversation function passed a null context"); return (PAM_CONV_ERR); } ctxt = data; if (n <= 0 || n > PAM_MAX_NUM_MSG) return (PAM_CONV_ERR); if ((reply = calloc(n, sizeof(*reply))) == NULL) return PAM_CONV_ERR; if ((buffer = sshbuf_new()) == NULL) { free(reply); return PAM_CONV_ERR; } for (i = 0; i < n; ++i) { switch (PAM_MSG_MEMBER(msg, i, msg_style)) { case PAM_PROMPT_ECHO_OFF: case PAM_PROMPT_ECHO_ON: if ((r = sshbuf_put_cstring(buffer, PAM_MSG_MEMBER(msg, i, msg))) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (ssh_msg_send(ctxt->pam_csock, PAM_MSG_MEMBER(msg, i, msg_style), buffer) == -1) goto fail; if (ssh_msg_recv(ctxt->pam_csock, buffer) == -1) goto fail; if ((r = sshbuf_get_u8(buffer, &status)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (status != PAM_AUTHTOK) goto fail; if ((r = sshbuf_get_cstring(buffer, &reply[i].resp, NULL)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); break; case PAM_ERROR_MSG: case PAM_TEXT_INFO: if ((r = sshbuf_put_cstring(buffer, PAM_MSG_MEMBER(msg, i, msg))) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (ssh_msg_send(ctxt->pam_csock, PAM_MSG_MEMBER(msg, i, msg_style), buffer) == -1) goto fail; break; default: goto fail; } sshbuf_reset(buffer); } sshbuf_free(buffer); *resp = reply; return (PAM_SUCCESS); fail: for(i = 0; i < n; i++) { free(reply[i].resp); } free(reply); sshbuf_free(buffer); return (PAM_CONV_ERR); } /* * Authentication thread. */ static void * sshpam_thread(void *ctxtp) { struct pam_ctxt *ctxt = ctxtp; struct sshbuf *buffer = NULL; struct pam_conv sshpam_conv; int r, flags = (options.permit_empty_passwd == 0 ? PAM_DISALLOW_NULL_AUTHTOK : 0); #ifndef UNSUPPORTED_POSIX_THREADS_HACK extern char **environ; char **env_from_pam; u_int i; const char *pam_user; const char **ptr_pam_user = &pam_user; char *tz = getenv("TZ"); sshpam_err = pam_get_item(sshpam_handle, PAM_USER, (sshpam_const void **)ptr_pam_user); if (sshpam_err != PAM_SUCCESS) goto auth_fail; environ[0] = NULL; if (tz != NULL) if (setenv("TZ", tz, 1) == -1) error("PAM: could not set TZ environment: %s", strerror(errno)); if (sshpam_authctxt != NULL) { setproctitle("%s [pam]", sshpam_authctxt->valid ? pam_user : "unknown"); } #endif sshpam_conv.conv = sshpam_thread_conv; sshpam_conv.appdata_ptr = ctxt; if (sshpam_authctxt == NULL) fatal("%s: PAM authctxt not initialized", __func__); if ((buffer = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, (const void *)&sshpam_conv); if (sshpam_err != PAM_SUCCESS) goto auth_fail; sshpam_err = pam_authenticate(sshpam_handle, flags); if (sshpam_err == PAM_MAXTRIES) sshpam_set_maxtries_reached(1); if (sshpam_err != PAM_SUCCESS) goto auth_fail; if (!do_pam_account()) { sshpam_err = PAM_ACCT_EXPIRED; goto auth_fail; } if (sshpam_authctxt->force_pwchange) { sshpam_err = pam_chauthtok(sshpam_handle, PAM_CHANGE_EXPIRED_AUTHTOK); if (sshpam_err != PAM_SUCCESS) goto auth_fail; sshpam_password_change_required(0); } if ((r = sshbuf_put_cstring(buffer, "OK")) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); #ifndef UNSUPPORTED_POSIX_THREADS_HACK /* Export variables set by do_pam_account */ if ((r = sshbuf_put_u32(buffer, sshpam_account_status)) != 0 || (r = sshbuf_put_u32(buffer, sshpam_authctxt->force_pwchange)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); /* Export any environment strings set in child */ for (i = 0; environ[i] != NULL; i++) { /* Count */ if (i > INT_MAX) fatal("%s: too many environment strings", __func__); } if ((r = sshbuf_put_u32(buffer, i)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); for (i = 0; environ[i] != NULL; i++) { if ((r = sshbuf_put_cstring(buffer, environ[i])) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); } /* Export any environment strings set by PAM in child */ env_from_pam = pam_getenvlist(sshpam_handle); for (i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) { /* Count */ if (i > INT_MAX) fatal("%s: too many PAM environment strings", __func__); } if ((r = sshbuf_put_u32(buffer, i)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); for (i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) { if ((r = sshbuf_put_cstring(buffer, env_from_pam[i])) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); } #endif /* UNSUPPORTED_POSIX_THREADS_HACK */ /* XXX - can't do much about an error here */ ssh_msg_send(ctxt->pam_csock, sshpam_err, buffer); sshbuf_free(buffer); pthread_exit(NULL); auth_fail: if ((r = sshbuf_put_cstring(buffer, pam_strerror(sshpam_handle, sshpam_err))) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); /* XXX - can't do much about an error here */ if (sshpam_err == PAM_ACCT_EXPIRED) ssh_msg_send(ctxt->pam_csock, PAM_ACCT_EXPIRED, buffer); else if (sshpam_maxtries_reached) ssh_msg_send(ctxt->pam_csock, PAM_MAXTRIES, buffer); else ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, buffer); sshbuf_free(buffer); pthread_exit(NULL); return (NULL); /* Avoid warning for non-pthread case */ } void sshpam_thread_cleanup(void) { struct pam_ctxt *ctxt = cleanup_ctxt; debug3("PAM: %s entering", __func__); if (ctxt != NULL && ctxt->pam_thread != 0) { pthread_cancel(ctxt->pam_thread); pthread_join(ctxt->pam_thread, NULL); close(ctxt->pam_psock); close(ctxt->pam_csock); memset(ctxt, 0, sizeof(*ctxt)); cleanup_ctxt = NULL; } } static int sshpam_null_conv(int n, sshpam_const struct pam_message **msg, struct pam_response **resp, void *data) { debug3("PAM: %s entering, %d messages", __func__, n); return (PAM_CONV_ERR); } static struct pam_conv null_conv = { sshpam_null_conv, NULL }; static int sshpam_store_conv(int n, sshpam_const struct pam_message **msg, struct pam_response **resp, void *data) { struct pam_response *reply; int r, i; debug3("PAM: %s called with %d messages", __func__, n); *resp = NULL; if (n <= 0 || n > PAM_MAX_NUM_MSG) return (PAM_CONV_ERR); if ((reply = calloc(n, sizeof(*reply))) == NULL) return (PAM_CONV_ERR); for (i = 0; i < n; ++i) { switch (PAM_MSG_MEMBER(msg, i, msg_style)) { case PAM_ERROR_MSG: case PAM_TEXT_INFO: if ((r = sshbuf_putf(loginmsg, "%s\n", PAM_MSG_MEMBER(msg, i, msg))) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); reply[i].resp_retcode = PAM_SUCCESS; break; default: goto fail; } } *resp = reply; return (PAM_SUCCESS); fail: for(i = 0; i < n; i++) { free(reply[i].resp); } free(reply); return (PAM_CONV_ERR); } static struct pam_conv store_conv = { sshpam_store_conv, NULL }; void sshpam_cleanup(void) { if (sshpam_handle == NULL || !mm_is_monitor()) return; debug("PAM: cleanup"); pam_set_item(sshpam_handle, PAM_CONV, (const void *)&null_conv); if (sshpam_session_open) { debug("PAM: closing session"); pam_close_session(sshpam_handle, PAM_SILENT); sshpam_session_open = 0; } if (sshpam_cred_established) { debug("PAM: deleting credentials"); pam_setcred(sshpam_handle, PAM_DELETE_CRED); sshpam_cred_established = 0; } sshpam_authenticated = 0; pam_end(sshpam_handle, sshpam_err); sshpam_handle = NULL; } static int sshpam_init(struct ssh *ssh, Authctxt *authctxt) { const char *pam_user, *user = authctxt->user; const char **ptr_pam_user = &pam_user; int r; if (options.pam_service_name == NULL) fatal_f("internal error: NULL PAM service name"); #if defined(PAM_SUN_CODEBASE) && defined(PAM_MAX_RESP_SIZE) /* Protect buggy PAM implementations from excessively long usernames */ if (strlen(user) >= PAM_MAX_RESP_SIZE) fatal("Username too long from %s port %d", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); #endif if (sshpam_handle == NULL) { if (ssh == NULL) { fatal("%s: called initially with no " "packet context", __func__); } } if (sshpam_handle != NULL) { /* We already have a PAM context; check if the user matches */ sshpam_err = pam_get_item(sshpam_handle, PAM_USER, (sshpam_const void **)ptr_pam_user); if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0) return (0); pam_end(sshpam_handle, sshpam_err); sshpam_handle = NULL; } debug("PAM: initializing for \"%s\" with service \"%s\"", user, options.pam_service_name); sshpam_err = pam_start(options.pam_service_name, user, &store_conv, &sshpam_handle); sshpam_authctxt = authctxt; if (sshpam_err != PAM_SUCCESS) { pam_end(sshpam_handle, sshpam_err); sshpam_handle = NULL; return (-1); } if (ssh != NULL && sshpam_rhost == NULL) { /* * We need to cache these as we don't have packet context * during the kbdint flow. */ sshpam_rhost = xstrdup(auth_get_canonical_hostname(ssh, options.use_dns)); sshpam_laddr = get_local_ipaddr( ssh_packet_get_connection_in(ssh)); } if (sshpam_rhost != NULL) { debug("PAM: setting PAM_RHOST to \"%s\"", sshpam_rhost); sshpam_err = pam_set_item(sshpam_handle, PAM_RHOST, sshpam_rhost); if (sshpam_err != PAM_SUCCESS) { pam_end(sshpam_handle, sshpam_err); sshpam_handle = NULL; return (-1); } } if (ssh != NULL && sshpam_laddr != NULL) { char *conninfo; /* Put SSH_CONNECTION in the PAM environment too */ xasprintf(&conninfo, "SSH_CONNECTION=%.50s %d %.50s %d", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), sshpam_laddr, ssh_local_port(ssh)); if ((r = pam_putenv(sshpam_handle, conninfo)) != PAM_SUCCESS) logit("pam_putenv: %s", pam_strerror(sshpam_handle, r)); free(conninfo); } #ifdef PAM_TTY_KLUDGE /* * Some silly PAM modules (e.g. pam_time) require a TTY to operate. * sshd doesn't set the tty until too late in the auth process and * may not even set one (for tty-less connections) */ debug("PAM: setting PAM_TTY to \"ssh\""); sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, "ssh"); if (sshpam_err != PAM_SUCCESS) { pam_end(sshpam_handle, sshpam_err); sshpam_handle = NULL; return (-1); } #endif return (0); } static void expose_authinfo(const char *caller) { char *auth_info; /* * Expose authentication information to PAM. * The environment variable is versioned. Please increment the * version suffix if the format of session_info changes. */ if (sshpam_authctxt->session_info == NULL) auth_info = xstrdup(""); else if ((auth_info = sshbuf_dup_string( sshpam_authctxt->session_info)) == NULL) fatal("%s: sshbuf_dup_string failed", __func__); debug2("%s: auth information in SSH_AUTH_INFO_0", caller); do_pam_putenv("SSH_AUTH_INFO_0", auth_info); free(auth_info); } static void * sshpam_init_ctx(Authctxt *authctxt) { struct pam_ctxt *ctxt; int result, socks[2]; debug3("PAM: %s entering", __func__); /* * Refuse to start if we don't have PAM enabled or do_pam_account * has previously failed. */ if (!options.use_pam || sshpam_account_status == 0) return NULL; /* Initialize PAM */ if (sshpam_init(NULL, authctxt) == -1) { error("PAM: initialization failed"); return (NULL); } expose_authinfo(__func__); ctxt = xcalloc(1, sizeof *ctxt); /* Start the authentication thread */ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, socks) == -1) { error("PAM: failed create sockets: %s", strerror(errno)); free(ctxt); return (NULL); } ctxt->pam_psock = socks[0]; ctxt->pam_csock = socks[1]; result = pthread_create(&ctxt->pam_thread, NULL, sshpam_thread, ctxt); if (result != 0) { error("PAM: failed to start authentication thread: %s", strerror(result)); close(socks[0]); close(socks[1]); free(ctxt); return (NULL); } cleanup_ctxt = ctxt; return (ctxt); } static int sshpam_query(void *ctx, char **name, char **info, u_int *num, char ***prompts, u_int **echo_on) { struct sshbuf *buffer; struct pam_ctxt *ctxt = ctx; size_t plen; u_char type; char *msg; size_t len, mlen, nmesg = 0; int r; debug3("PAM: %s entering", __func__); if ((buffer = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); *name = xstrdup(""); *info = xstrdup(""); *prompts = xmalloc(sizeof(char *)); **prompts = NULL; plen = 0; *echo_on = xmalloc(sizeof(u_int)); while (ssh_msg_recv(ctxt->pam_psock, buffer) == 0) { if (++nmesg > PAM_MAX_NUM_MSG) fatal_f("too many query messages"); if ((r = sshbuf_get_u8(buffer, &type)) != 0 || (r = sshbuf_get_cstring(buffer, &msg, &mlen)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); switch (type) { case PAM_PROMPT_ECHO_ON: case PAM_PROMPT_ECHO_OFF: *num = 1; len = plen + mlen + 1; **prompts = xreallocarray(**prompts, 1, len); strlcpy(**prompts + plen, msg, len - plen); plen += mlen; **echo_on = (type == PAM_PROMPT_ECHO_ON); free(msg); sshbuf_free(buffer); return (0); case PAM_ERROR_MSG: case PAM_TEXT_INFO: /* accumulate messages */ len = plen + mlen + 2; **prompts = xreallocarray(**prompts, 1, len); strlcpy(**prompts + plen, msg, len - plen); plen += mlen; strlcat(**prompts + plen, "\n", len - plen); plen++; free(msg); break; case PAM_ACCT_EXPIRED: case PAM_MAXTRIES: if (type == PAM_ACCT_EXPIRED) sshpam_account_status = 0; if (type == PAM_MAXTRIES) sshpam_set_maxtries_reached(1); /* FALLTHROUGH */ case PAM_AUTH_ERR: debug3("PAM: %s", pam_strerror(sshpam_handle, type)); if (**prompts != NULL && strlen(**prompts) != 0) { free(*info); *info = **prompts; **prompts = NULL; *num = 0; **echo_on = 0; ctxt->pam_done = -1; free(msg); sshbuf_free(buffer); return 0; } /* FALLTHROUGH */ case PAM_SUCCESS: if (**prompts != NULL) { /* drain any accumulated messages */ debug("PAM: %s", **prompts); if ((r = sshbuf_put(loginmsg, **prompts, strlen(**prompts))) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); free(**prompts); **prompts = NULL; } if (type == PAM_SUCCESS) { if (!sshpam_authctxt->valid || (sshpam_authctxt->pw->pw_uid == 0 && options.permit_root_login != PERMIT_YES)) fatal("Internal error: PAM auth " "succeeded when it should have " "failed"); import_environments(buffer); *num = 0; **echo_on = 0; ctxt->pam_done = 1; free(msg); sshbuf_free(buffer); return (0); } - BLACKLIST_NOTIFY(NULL, BLACKLIST_BAD_USER, - sshpam_authctxt->user); + BLACKLIST_NOTIFY(NULL, BLACKLIST_AUTH_FAIL, + "PAM illegal user"); error("PAM: %s for %s%.100s from %.100s", msg, sshpam_authctxt->valid ? "" : "illegal user ", sshpam_authctxt->user, sshpam_rhost); /* FALLTHROUGH */ default: *num = 0; **echo_on = 0; free(msg); ctxt->pam_done = -1; sshbuf_free(buffer); return (-1); } } sshbuf_free(buffer); return (-1); } /* * Returns a junk password of identical length to that the user supplied. * Used to mitigate timing attacks against crypt(3)/PAM stacks that * vary processing time in proportion to password length. */ static char * fake_password(const char *wire_password) { const char junk[] = "\b\n\r\177INCORRECT"; char *ret = NULL; size_t i, l = wire_password != NULL ? strlen(wire_password) : 0; if (l >= INT_MAX) fatal("%s: password length too long: %zu", __func__, l); ret = malloc(l + 1); if (ret == NULL) return NULL; for (i = 0; i < l; i++) ret[i] = junk[i % (sizeof(junk) - 1)]; ret[i] = '\0'; return ret; } /* XXX - see also comment in auth-chall.c:verify_response */ static int sshpam_respond(void *ctx, u_int num, char **resp) { struct sshbuf *buffer; struct pam_ctxt *ctxt = ctx; char *fake; int r; debug2("PAM: %s entering, %u responses", __func__, num); switch (ctxt->pam_done) { case 1: sshpam_authenticated = 1; return (0); case 0: break; default: return (-1); } if (num != 1) { error("PAM: expected one response, got %u", num); return (-1); } if ((buffer = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); if (sshpam_authctxt->valid && (sshpam_authctxt->pw->pw_uid != 0 || options.permit_root_login == PERMIT_YES)) { if ((r = sshbuf_put_cstring(buffer, *resp)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); } else { fake = fake_password(*resp); if ((r = sshbuf_put_cstring(buffer, fake)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); free(fake); } if (ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, buffer) == -1) { sshbuf_free(buffer); return (-1); } sshbuf_free(buffer); return (1); } static void sshpam_free_ctx(void *ctxtp) { struct pam_ctxt *ctxt = ctxtp; debug3("PAM: %s entering", __func__); sshpam_thread_cleanup(); free(ctxt); /* * We don't call sshpam_cleanup() here because we may need the PAM * handle at a later stage, e.g. when setting up a session. It's * still on the cleanup list, so pam_end() *will* be called before * the server process terminates. */ } KbdintDevice sshpam_device = { "pam", sshpam_init_ctx, sshpam_query, sshpam_respond, sshpam_free_ctx }; KbdintDevice mm_sshpam_device = { "pam", mm_sshpam_init_ctx, mm_sshpam_query, mm_sshpam_respond, mm_sshpam_free_ctx }; /* * This replaces auth-pam.c */ void start_pam(struct ssh *ssh) { Authctxt *authctxt = (Authctxt *)ssh->authctxt; if (!options.use_pam) fatal("PAM: initialisation requested when UsePAM=no"); if (sshpam_init(ssh, authctxt) == -1) fatal("PAM: initialisation failed"); } void finish_pam(void) { sshpam_cleanup(); } u_int do_pam_account(void) { debug("%s: called", __func__); if (sshpam_account_status != -1) return (sshpam_account_status); expose_authinfo(__func__); sshpam_err = pam_acct_mgmt(sshpam_handle, 0); debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err, pam_strerror(sshpam_handle, sshpam_err)); if (sshpam_err != PAM_SUCCESS && sshpam_err != PAM_NEW_AUTHTOK_REQD) { sshpam_account_status = 0; return (sshpam_account_status); } if (sshpam_err == PAM_NEW_AUTHTOK_REQD) sshpam_password_change_required(1); sshpam_account_status = 1; return (sshpam_account_status); } void do_pam_setcred(void) { sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, (const void *)&store_conv); if (sshpam_err != PAM_SUCCESS) fatal("PAM: failed to set PAM_CONV: %s", pam_strerror(sshpam_handle, sshpam_err)); debug("PAM: establishing credentials"); sshpam_err = pam_setcred(sshpam_handle, PAM_ESTABLISH_CRED); if (sshpam_err == PAM_SUCCESS) { sshpam_cred_established = 1; return; } if (sshpam_authenticated) fatal("PAM: pam_setcred(): %s", pam_strerror(sshpam_handle, sshpam_err)); else debug("PAM: pam_setcred(): %s", pam_strerror(sshpam_handle, sshpam_err)); } #if 0 static int sshpam_tty_conv(int n, sshpam_const struct pam_message **msg, struct pam_response **resp, void *data) { char input[PAM_MAX_MSG_SIZE]; struct pam_response *reply; int i; debug3("PAM: %s called with %d messages", __func__, n); *resp = NULL; if (n <= 0 || n > PAM_MAX_NUM_MSG || !isatty(STDIN_FILENO)) return (PAM_CONV_ERR); if ((reply = calloc(n, sizeof(*reply))) == NULL) return (PAM_CONV_ERR); for (i = 0; i < n; ++i) { switch (PAM_MSG_MEMBER(msg, i, msg_style)) { case PAM_PROMPT_ECHO_OFF: reply[i].resp = read_passphrase(PAM_MSG_MEMBER(msg, i, msg), RP_ALLOW_STDIN); reply[i].resp_retcode = PAM_SUCCESS; break; case PAM_PROMPT_ECHO_ON: fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg)); if (fgets(input, sizeof input, stdin) == NULL) input[0] = '\0'; if ((reply[i].resp = strdup(input)) == NULL) goto fail; reply[i].resp_retcode = PAM_SUCCESS; break; case PAM_ERROR_MSG: case PAM_TEXT_INFO: fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg)); reply[i].resp_retcode = PAM_SUCCESS; break; default: goto fail; } } *resp = reply; return (PAM_SUCCESS); fail: for(i = 0; i < n; i++) { free(reply[i].resp); } free(reply); return (PAM_CONV_ERR); } static struct pam_conv tty_conv = { sshpam_tty_conv, NULL }; #endif /* * XXX this should be done in the authentication phase, but ssh1 doesn't * support that */ void do_pam_chauthtok(void) { fatal("Password expired"); #if 0 sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, (const void *)&tty_conv); if (sshpam_err != PAM_SUCCESS) fatal("PAM: failed to set PAM_CONV: %s", pam_strerror(sshpam_handle, sshpam_err)); debug("PAM: changing password"); sshpam_err = pam_chauthtok(sshpam_handle, PAM_CHANGE_EXPIRED_AUTHTOK); if (sshpam_err != PAM_SUCCESS) fatal("PAM: pam_chauthtok(): %s", pam_strerror(sshpam_handle, sshpam_err)); #endif } void do_pam_session(struct ssh *ssh) { debug3("PAM: opening session"); expose_authinfo(__func__); sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, (const void *)&store_conv); if (sshpam_err != PAM_SUCCESS) fatal("PAM: failed to set PAM_CONV: %s", pam_strerror(sshpam_handle, sshpam_err)); sshpam_err = pam_open_session(sshpam_handle, 0); if (sshpam_err == PAM_SUCCESS) sshpam_session_open = 1; else { sshpam_session_open = 0; auth_restrict_session(ssh); error("PAM: pam_open_session(): %s", pam_strerror(sshpam_handle, sshpam_err)); } } int is_pam_session_open(void) { return sshpam_session_open; } /* * Set a PAM environment string. We need to do this so that the session * modules can handle things like Kerberos/GSI credentials that appear * during the ssh authentication process. */ int do_pam_putenv(char *name, char *value) { int ret = 1; char *compound; size_t len; len = strlen(name) + strlen(value) + 2; compound = xmalloc(len); snprintf(compound, len, "%s=%s", name, value); ret = pam_putenv(sshpam_handle, compound); free(compound); return (ret); } char ** fetch_pam_child_environment(void) { return sshpam_env; } char ** fetch_pam_environment(void) { return (pam_getenvlist(sshpam_handle)); } void free_pam_environment(char **env) { char **envp; if (env == NULL) return; for (envp = env; *envp; envp++) free(*envp); free(env); } /* * "Blind" conversation function for password authentication. Assumes that * echo-off prompts are for the password and stores messages for later * display. */ static int sshpam_passwd_conv(int n, sshpam_const struct pam_message **msg, struct pam_response **resp, void *data) { struct pam_response *reply; int r, i; size_t len; debug3("PAM: %s called with %d messages", __func__, n); *resp = NULL; if (n <= 0 || n > PAM_MAX_NUM_MSG) return (PAM_CONV_ERR); if ((reply = calloc(n, sizeof(*reply))) == NULL) return (PAM_CONV_ERR); for (i = 0; i < n; ++i) { switch (PAM_MSG_MEMBER(msg, i, msg_style)) { case PAM_PROMPT_ECHO_OFF: if (sshpam_password == NULL) goto fail; if ((reply[i].resp = strdup(sshpam_password)) == NULL) goto fail; reply[i].resp_retcode = PAM_SUCCESS; break; case PAM_ERROR_MSG: case PAM_TEXT_INFO: len = strlen(PAM_MSG_MEMBER(msg, i, msg)); if (len > 0) { if ((r = sshbuf_putf(loginmsg, "%s\n", PAM_MSG_MEMBER(msg, i, msg))) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); } if ((reply[i].resp = strdup("")) == NULL) goto fail; reply[i].resp_retcode = PAM_SUCCESS; break; default: goto fail; } } *resp = reply; return (PAM_SUCCESS); fail: for(i = 0; i < n; i++) { free(reply[i].resp); } free(reply); return (PAM_CONV_ERR); } static struct pam_conv passwd_conv = { sshpam_passwd_conv, NULL }; /* * Attempt password authentication via PAM */ int sshpam_auth_passwd(Authctxt *authctxt, const char *password) { int flags = (options.permit_empty_passwd == 0 ? PAM_DISALLOW_NULL_AUTHTOK : 0); char *fake = NULL; if (!options.use_pam || sshpam_handle == NULL) fatal("PAM: %s called when PAM disabled or failed to " "initialise.", __func__); sshpam_password = password; sshpam_authctxt = authctxt; /* * If the user logging in is invalid, or is root but is not permitted * by PermitRootLogin, use an invalid password to prevent leaking * information via timing (eg if the PAM config has a delay on fail). */ if (!authctxt->valid || (authctxt->pw->pw_uid == 0 && options.permit_root_login != PERMIT_YES)) sshpam_password = fake = fake_password(password); sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, (const void *)&passwd_conv); if (sshpam_err != PAM_SUCCESS) fatal("PAM: %s: failed to set PAM_CONV: %s", __func__, pam_strerror(sshpam_handle, sshpam_err)); expose_authinfo(__func__); sshpam_err = pam_authenticate(sshpam_handle, flags); sshpam_password = NULL; free(fake); if (sshpam_err == PAM_MAXTRIES) sshpam_set_maxtries_reached(1); if (sshpam_err == PAM_SUCCESS && authctxt->valid) { debug("PAM: password authentication accepted for %.100s", authctxt->user); return 1; } else { debug("PAM: password authentication failed for %.100s: %s", authctxt->valid ? authctxt->user : "an illegal user", pam_strerror(sshpam_handle, sshpam_err)); return 0; } } int sshpam_get_maxtries_reached(void) { return sshpam_maxtries_reached; } void sshpam_set_maxtries_reached(int reached) { if (reached == 0 || sshpam_maxtries_reached) return; sshpam_maxtries_reached = 1; options.password_authentication = 0; options.kbd_interactive_authentication = 0; } #endif /* USE_PAM */ diff --git a/crypto/openssh/auth.c b/crypto/openssh/auth.c index 961082b76667..0a1c8f71b390 100644 --- a/crypto/openssh/auth.c +++ b/crypto/openssh/auth.c @@ -1,786 +1,788 @@ /* $OpenBSD: auth.c,v 1.162 2024/09/15 01:18:26 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #include #include #include #include #ifdef HAVE_PATHS_H # include #endif #include #ifdef HAVE_LOGIN_H #include #endif #ifdef USE_SHADOW #include #endif #include #include #include #include #include #include #include #include "xmalloc.h" #include "match.h" #include "groupaccess.h" #include "log.h" #include "sshbuf.h" #include "misc.h" #include "servconf.h" #include "sshkey.h" #include "hostfile.h" #include "auth.h" #include "auth-options.h" #include "canohost.h" #include "uidswap.h" #include "packet.h" #include "loginrec.h" #ifdef GSSAPI #include "ssh-gss.h" #endif #include "authfile.h" #include "monitor_wrap.h" #include "ssherr.h" #include "channels.h" #include "blacklist_client.h" /* import */ extern ServerOptions options; extern struct include_list includes; extern struct sshbuf *loginmsg; extern struct passwd *privsep_pw; extern struct sshauthopt *auth_opts; /* Debugging messages */ static struct sshbuf *auth_debug; /* * Check if the user is allowed to log in via ssh. If user is listed * in DenyUsers or one of user's groups is listed in DenyGroups, false * will be returned. If AllowUsers isn't empty and user isn't listed * there, or if AllowGroups isn't empty and one of user's groups isn't * listed there, false will be returned. * If the user's shell is not executable, false will be returned. * Otherwise true is returned. */ int allowed_user(struct ssh *ssh, struct passwd * pw) { struct stat st; const char *hostname = NULL, *ipaddr = NULL; u_int i; int r; /* Shouldn't be called if pw is NULL, but better safe than sorry... */ if (!pw || !pw->pw_name) return 0; if (!options.use_pam && platform_locked_account(pw)) { logit("User %.100s not allowed because account is locked", pw->pw_name); return 0; } /* * Deny if shell does not exist or is not executable unless we * are chrooting. */ if (options.chroot_directory == NULL || strcasecmp(options.chroot_directory, "none") == 0) { char *shell = xstrdup((pw->pw_shell[0] == '\0') ? _PATH_BSHELL : pw->pw_shell); /* empty = /bin/sh */ if (stat(shell, &st) == -1) { logit("User %.100s not allowed because shell %.100s " "does not exist", pw->pw_name, shell); free(shell); return 0; } if (S_ISREG(st.st_mode) == 0 || (st.st_mode & (S_IXOTH|S_IXUSR|S_IXGRP)) == 0) { logit("User %.100s not allowed because shell %.100s " "is not executable", pw->pw_name, shell); free(shell); return 0; } free(shell); } if (options.num_deny_users > 0 || options.num_allow_users > 0 || options.num_deny_groups > 0 || options.num_allow_groups > 0) { hostname = auth_get_canonical_hostname(ssh, options.use_dns); ipaddr = ssh_remote_ipaddr(ssh); } /* Return false if user is listed in DenyUsers */ if (options.num_deny_users > 0) { for (i = 0; i < options.num_deny_users; i++) { r = match_user(pw->pw_name, hostname, ipaddr, options.deny_users[i]); if (r < 0) { fatal("Invalid DenyUsers pattern \"%.100s\"", options.deny_users[i]); } else if (r != 0) { logit("User %.100s from %.100s not allowed " "because listed in DenyUsers", pw->pw_name, hostname); return 0; } } } /* Return false if AllowUsers isn't empty and user isn't listed there */ if (options.num_allow_users > 0) { for (i = 0; i < options.num_allow_users; i++) { r = match_user(pw->pw_name, hostname, ipaddr, options.allow_users[i]); if (r < 0) { fatal("Invalid AllowUsers pattern \"%.100s\"", options.allow_users[i]); } else if (r == 1) break; } /* i < options.num_allow_users iff we break for loop */ if (i >= options.num_allow_users) { logit("User %.100s from %.100s not allowed because " "not listed in AllowUsers", pw->pw_name, hostname); return 0; } } if (options.num_deny_groups > 0 || options.num_allow_groups > 0) { /* Get the user's group access list (primary and supplementary) */ if (ga_init(pw->pw_name, pw->pw_gid) == 0) { logit("User %.100s from %.100s not allowed because " "not in any group", pw->pw_name, hostname); return 0; } /* Return false if one of user's groups is listed in DenyGroups */ if (options.num_deny_groups > 0) if (ga_match(options.deny_groups, options.num_deny_groups)) { ga_free(); logit("User %.100s from %.100s not allowed " "because a group is listed in DenyGroups", pw->pw_name, hostname); return 0; } /* * Return false if AllowGroups isn't empty and one of user's groups * isn't listed there */ if (options.num_allow_groups > 0) if (!ga_match(options.allow_groups, options.num_allow_groups)) { ga_free(); logit("User %.100s from %.100s not allowed " "because none of user's groups are listed " "in AllowGroups", pw->pw_name, hostname); return 0; } ga_free(); } #ifdef CUSTOM_SYS_AUTH_ALLOWED_USER if (!sys_auth_allowed_user(pw, loginmsg)) return 0; #endif /* We found no reason not to let this user try to log on... */ return 1; } /* * Formats any key left in authctxt->auth_method_key for inclusion in * auth_log()'s message. Also includes authxtct->auth_method_info if present. */ static char * format_method_key(Authctxt *authctxt) { const struct sshkey *key = authctxt->auth_method_key; const char *methinfo = authctxt->auth_method_info; char *fp, *cafp, *ret = NULL; if (key == NULL) return NULL; if (sshkey_is_cert(key)) { fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_DEFAULT); cafp = sshkey_fingerprint(key->cert->signature_key, options.fingerprint_hash, SSH_FP_DEFAULT); xasprintf(&ret, "%s %s ID %s (serial %llu) CA %s %s%s%s", sshkey_type(key), fp == NULL ? "(null)" : fp, key->cert->key_id, (unsigned long long)key->cert->serial, sshkey_type(key->cert->signature_key), cafp == NULL ? "(null)" : cafp, methinfo == NULL ? "" : ", ", methinfo == NULL ? "" : methinfo); free(fp); free(cafp); } else { fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_DEFAULT); xasprintf(&ret, "%s %s%s%s", sshkey_type(key), fp == NULL ? "(null)" : fp, methinfo == NULL ? "" : ", ", methinfo == NULL ? "" : methinfo); free(fp); } return ret; } void auth_log(struct ssh *ssh, int authenticated, int partial, const char *method, const char *submethod) { Authctxt *authctxt = (Authctxt *)ssh->authctxt; int level = SYSLOG_LEVEL_VERBOSE; const char *authmsg; char *extra = NULL; if (!mm_is_monitor() && !authctxt->postponed) return; /* Raise logging level */ if (authenticated == 1 || !authctxt->valid || authctxt->failures >= options.max_authtries / 2 || strcmp(method, "password") == 0) level = SYSLOG_LEVEL_INFO; if (authctxt->postponed) authmsg = "Postponed"; else if (partial) authmsg = "Partial"; else { authmsg = authenticated ? "Accepted" : "Failed"; if (authenticated) - BLACKLIST_NOTIFY(ssh, BLACKLIST_AUTH_OK, "ssh"); + BLACKLIST_NOTIFY(ssh, BLACKLIST_AUTH_OK, + "Authenticated"); } if ((extra = format_method_key(authctxt)) == NULL) { if (authctxt->auth_method_info != NULL) extra = xstrdup(authctxt->auth_method_info); } do_log2(level, "%s %s%s%s for %s%.100s from %.200s port %d ssh2%s%s", authmsg, method, submethod != NULL ? "/" : "", submethod == NULL ? "" : submethod, authctxt->valid ? "" : "invalid user ", authctxt->user, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), extra != NULL ? ": " : "", extra != NULL ? extra : ""); free(extra); #if defined(CUSTOM_FAILED_LOGIN) || defined(SSH_AUDIT_EVENTS) if (authenticated == 0 && !(authctxt->postponed || partial)) { /* Log failed login attempt */ # ifdef CUSTOM_FAILED_LOGIN if (strcmp(method, "password") == 0 || strncmp(method, "keyboard-interactive", 20) == 0 || strcmp(method, "challenge-response") == 0) record_failed_login(ssh, authctxt->user, auth_get_canonical_hostname(ssh, options.use_dns), "ssh"); # endif # ifdef SSH_AUDIT_EVENTS audit_event(ssh, audit_classify_auth(method)); # endif } #endif #if defined(CUSTOM_FAILED_LOGIN) && defined(WITH_AIXAUTHENTICATE) if (authenticated) sys_auth_record_login(authctxt->user, auth_get_canonical_hostname(ssh, options.use_dns), "ssh", loginmsg); #endif } void auth_maxtries_exceeded(struct ssh *ssh) { Authctxt *authctxt = (Authctxt *)ssh->authctxt; + BLACKLIST_NOTIFY(ssh, BLACKLIST_AUTH_FAIL, "Maximum attempts exceeded"); error("maximum authentication attempts exceeded for " "%s%.100s from %.200s port %d ssh2", authctxt->valid ? "" : "invalid user ", authctxt->user, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); ssh_packet_disconnect(ssh, "Too many authentication failures"); /* NOTREACHED */ } /* * Check whether root logins are disallowed. */ int auth_root_allowed(struct ssh *ssh, const char *method) { switch (options.permit_root_login) { case PERMIT_YES: return 1; case PERMIT_NO_PASSWD: if (strcmp(method, "publickey") == 0 || strcmp(method, "hostbased") == 0 || strcmp(method, "gssapi-with-mic") == 0) return 1; break; case PERMIT_FORCED_ONLY: if (auth_opts->force_command != NULL) { logit("Root login accepted for forced command."); return 1; } break; } logit("ROOT LOGIN REFUSED FROM %.200s port %d", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); return 0; } /* * Given a template and a passwd structure, build a filename * by substituting % tokenised options. Currently, %% becomes '%', * %h becomes the home directory and %u the username. * * This returns a buffer allocated by xmalloc. */ char * expand_authorized_keys(const char *filename, struct passwd *pw) { char *file, uidstr[32], ret[PATH_MAX]; int i; snprintf(uidstr, sizeof(uidstr), "%llu", (unsigned long long)pw->pw_uid); file = percent_expand(filename, "h", pw->pw_dir, "u", pw->pw_name, "U", uidstr, (char *)NULL); /* * Ensure that filename starts anchored. If not, be backward * compatible and prepend the '%h/' */ if (path_absolute(file)) return (file); i = snprintf(ret, sizeof(ret), "%s/%s", pw->pw_dir, file); if (i < 0 || (size_t)i >= sizeof(ret)) fatal("expand_authorized_keys: path too long"); free(file); return (xstrdup(ret)); } char * authorized_principals_file(struct passwd *pw) { if (options.authorized_principals_file == NULL) return NULL; return expand_authorized_keys(options.authorized_principals_file, pw); } /* return ok if key exists in sysfile or userfile */ HostStatus check_key_in_hostfiles(struct passwd *pw, struct sshkey *key, const char *host, const char *sysfile, const char *userfile) { char *user_hostfile; struct stat st; HostStatus host_status; struct hostkeys *hostkeys; const struct hostkey_entry *found; hostkeys = init_hostkeys(); load_hostkeys(hostkeys, host, sysfile, 0); if (userfile != NULL) { user_hostfile = tilde_expand_filename(userfile, pw->pw_uid); if (options.strict_modes && (stat(user_hostfile, &st) == 0) && ((st.st_uid != 0 && st.st_uid != pw->pw_uid) || (st.st_mode & 022) != 0)) { logit("Authentication refused for %.100s: " "bad owner or modes for %.200s", pw->pw_name, user_hostfile); auth_debug_add("Ignored %.200s: bad ownership or modes", user_hostfile); } else { temporarily_use_uid(pw); load_hostkeys(hostkeys, host, user_hostfile, 0); restore_uid(); } free(user_hostfile); } host_status = check_key_in_hostkeys(hostkeys, key, &found); if (host_status == HOST_REVOKED) error("WARNING: revoked key for %s attempted authentication", host); else if (host_status == HOST_OK) debug_f("key for %s found at %s:%ld", found->host, found->file, found->line); else debug_f("key for host %s not found", host); free_hostkeys(hostkeys); return host_status; } struct passwd * getpwnamallow(struct ssh *ssh, const char *user) { #ifdef HAVE_LOGIN_CAP extern login_cap_t *lc; #ifdef HAVE_AUTH_HOSTOK const char *from_host, *from_ip; #endif #ifdef BSD_AUTH auth_session_t *as; #endif #endif struct passwd *pw; struct connection_info *ci; u_int i; ci = server_get_connection_info(ssh, 1, options.use_dns); ci->user = user; ci->user_invalid = getpwnam(user) == NULL; parse_server_match_config(&options, &includes, ci); log_change_level(options.log_level); log_verbose_reset(); for (i = 0; i < options.num_log_verbose; i++) log_verbose_add(options.log_verbose[i]); server_process_permitopen(ssh); #if defined(_AIX) && defined(HAVE_SETAUTHDB) aix_setauthdb(user); #endif pw = getpwnam(user); #if defined(_AIX) && defined(HAVE_SETAUTHDB) aix_restoreauthdb(); #endif if (pw == NULL) { - BLACKLIST_NOTIFY(ssh, BLACKLIST_BAD_USER, user); + BLACKLIST_NOTIFY(ssh, BLACKLIST_AUTH_FAIL, "Invalid user"); logit("Invalid user %.100s from %.100s port %d", user, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); #ifdef CUSTOM_FAILED_LOGIN record_failed_login(ssh, user, auth_get_canonical_hostname(ssh, options.use_dns), "ssh"); #endif #ifdef SSH_AUDIT_EVENTS audit_event(ssh, SSH_INVALID_USER); #endif /* SSH_AUDIT_EVENTS */ return (NULL); } if (!allowed_user(ssh, pw)) return (NULL); #ifdef HAVE_LOGIN_CAP if ((lc = login_getpwclass(pw)) == NULL) { debug("unable to get login class: %s", user); return (NULL); } #ifdef HAVE_AUTH_HOSTOK from_host = auth_get_canonical_hostname(ssh, options.use_dns); from_ip = ssh_remote_ipaddr(ssh); if (!auth_hostok(lc, from_host, from_ip)) { debug("Denied connection for %.200s from %.200s [%.200s].", pw->pw_name, from_host, from_ip); return (NULL); } #endif /* HAVE_AUTH_HOSTOK */ #ifdef HAVE_AUTH_TIMEOK if (!auth_timeok(lc, time(NULL))) { debug("LOGIN %.200s REFUSED (TIME)", pw->pw_name); return (NULL); } #endif /* HAVE_AUTH_TIMEOK */ #ifdef BSD_AUTH if ((as = auth_open()) == NULL || auth_setpwd(as, pw) != 0 || auth_approval(as, lc, pw->pw_name, "ssh") <= 0) { debug("Approval failure for %s", user); pw = NULL; } if (as != NULL) auth_close(as); #endif #endif if (pw != NULL) return (pwcopy(pw)); return (NULL); } /* Returns 1 if key is revoked by revoked_keys_file, 0 otherwise */ int auth_key_is_revoked(struct sshkey *key) { char *fp = NULL; int r; if (options.revoked_keys_file == NULL) return 0; if ((fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) { r = SSH_ERR_ALLOC_FAIL; error_fr(r, "fingerprint key"); goto out; } r = sshkey_check_revoked(key, options.revoked_keys_file); switch (r) { case 0: break; /* not revoked */ case SSH_ERR_KEY_REVOKED: error("Authentication key %s %s revoked by file %s", sshkey_type(key), fp, options.revoked_keys_file); goto out; default: error_r(r, "Error checking authentication key %s %s in " "revoked keys file %s", sshkey_type(key), fp, options.revoked_keys_file); goto out; } /* Success */ r = 0; out: free(fp); return r == 0 ? 0 : 1; } void auth_debug_add(const char *fmt,...) { char buf[1024]; va_list args; int r; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); debug3("%s", buf); if (auth_debug != NULL) if ((r = sshbuf_put_cstring(auth_debug, buf)) != 0) fatal_fr(r, "sshbuf_put_cstring"); } void auth_debug_send(struct ssh *ssh) { char *msg; int r; if (auth_debug == NULL) return; while (sshbuf_len(auth_debug) != 0) { if ((r = sshbuf_get_cstring(auth_debug, &msg, NULL)) != 0) fatal_fr(r, "sshbuf_get_cstring"); ssh_packet_send_debug(ssh, "%s", msg); free(msg); } } void auth_debug_reset(void) { if (auth_debug != NULL) sshbuf_reset(auth_debug); else if ((auth_debug = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); } struct passwd * fakepw(void) { static int done = 0; static struct passwd fake; const char hashchars[] = "./ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz0123456789"; /* from bcrypt.c */ char *cp; if (done) return (&fake); memset(&fake, 0, sizeof(fake)); fake.pw_name = "NOUSER"; fake.pw_passwd = xstrdup("$2a$10$" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); for (cp = fake.pw_passwd + 7; *cp != '\0'; cp++) *cp = hashchars[arc4random_uniform(sizeof(hashchars) - 1)]; #ifdef HAVE_STRUCT_PASSWD_PW_GECOS fake.pw_gecos = "NOUSER"; #endif fake.pw_uid = privsep_pw == NULL ? (uid_t)-1 : privsep_pw->pw_uid; fake.pw_gid = privsep_pw == NULL ? (gid_t)-1 : privsep_pw->pw_gid; #ifdef HAVE_STRUCT_PASSWD_PW_CLASS fake.pw_class = ""; #endif fake.pw_dir = "/nonexist"; fake.pw_shell = "/nonexist"; done = 1; return (&fake); } /* * Return the canonical name of the host in the other side of the current * connection. The host name is cached, so it is efficient to call this * several times. */ const char * auth_get_canonical_hostname(struct ssh *ssh, int use_dns) { static char *dnsname; if (!use_dns) return ssh_remote_ipaddr(ssh); if (dnsname != NULL) return dnsname; dnsname = ssh_remote_hostname(ssh); return dnsname; } /* These functions link key/cert options to the auth framework */ /* Log sshauthopt options locally and (optionally) for remote transmission */ void auth_log_authopts(const char *loc, const struct sshauthopt *opts, int do_remote) { int do_env = options.permit_user_env && opts->nenv > 0; int do_permitopen = opts->npermitopen > 0 && (options.allow_tcp_forwarding & FORWARD_LOCAL) != 0; int do_permitlisten = opts->npermitlisten > 0 && (options.allow_tcp_forwarding & FORWARD_REMOTE) != 0; size_t i; char msg[1024], buf[64]; snprintf(buf, sizeof(buf), "%d", opts->force_tun_device); /* Try to keep this alphabetically sorted */ snprintf(msg, sizeof(msg), "key options:%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", opts->permit_agent_forwarding_flag ? " agent-forwarding" : "", opts->force_command == NULL ? "" : " command", do_env ? " environment" : "", opts->valid_before == 0 ? "" : "expires", opts->no_require_user_presence ? " no-touch-required" : "", do_permitopen ? " permitopen" : "", do_permitlisten ? " permitlisten" : "", opts->permit_port_forwarding_flag ? " port-forwarding" : "", opts->cert_principals == NULL ? "" : " principals", opts->permit_pty_flag ? " pty" : "", opts->require_verify ? " uv" : "", opts->force_tun_device == -1 ? "" : " tun=", opts->force_tun_device == -1 ? "" : buf, opts->permit_user_rc ? " user-rc" : "", opts->permit_x11_forwarding_flag ? " x11-forwarding" : ""); debug("%s: %s", loc, msg); if (do_remote) auth_debug_add("%s: %s", loc, msg); if (options.permit_user_env) { for (i = 0; i < opts->nenv; i++) { debug("%s: environment: %s", loc, opts->env[i]); if (do_remote) { auth_debug_add("%s: environment: %s", loc, opts->env[i]); } } } /* Go into a little more details for the local logs. */ if (opts->valid_before != 0) { format_absolute_time(opts->valid_before, buf, sizeof(buf)); debug("%s: expires at %s", loc, buf); } if (opts->cert_principals != NULL) { debug("%s: authorized principals: \"%s\"", loc, opts->cert_principals); } if (opts->force_command != NULL) debug("%s: forced command: \"%s\"", loc, opts->force_command); if (do_permitopen) { for (i = 0; i < opts->npermitopen; i++) { debug("%s: permitted open: %s", loc, opts->permitopen[i]); } } if (do_permitlisten) { for (i = 0; i < opts->npermitlisten; i++) { debug("%s: permitted listen: %s", loc, opts->permitlisten[i]); } } } /* Activate a new set of key/cert options; merging with what is there. */ int auth_activate_options(struct ssh *ssh, struct sshauthopt *opts) { struct sshauthopt *old = auth_opts; const char *emsg = NULL; debug_f("setting new authentication options"); if ((auth_opts = sshauthopt_merge(old, opts, &emsg)) == NULL) { error("Inconsistent authentication options: %s", emsg); return -1; } return 0; } /* Disable forwarding, etc for the session */ void auth_restrict_session(struct ssh *ssh) { struct sshauthopt *restricted; debug_f("restricting session"); /* A blank sshauthopt defaults to permitting nothing */ if ((restricted = sshauthopt_new()) == NULL) fatal_f("sshauthopt_new failed"); restricted->permit_pty_flag = 1; restricted->restricted = 1; if (auth_activate_options(ssh, restricted) != 0) fatal_f("failed to restrict session"); sshauthopt_free(restricted); } diff --git a/crypto/openssh/auth2.c b/crypto/openssh/auth2.c index eac1d26a4aaf..82f6e6211259 100644 --- a/crypto/openssh/auth2.c +++ b/crypto/openssh/auth2.c @@ -1,811 +1,808 @@ /* $OpenBSD: auth2.c,v 1.170 2025/01/17 00:09:41 dtucker Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #include #include #include #include #include #include #include "stdlib.h" #include "atomicio.h" #include "xmalloc.h" #include "ssh2.h" #include "packet.h" #include "log.h" #include "sshbuf.h" #include "misc.h" #include "servconf.h" #include "sshkey.h" #include "hostfile.h" #include "auth.h" #include "dispatch.h" #include "pathnames.h" #include "ssherr.h" -#include "blacklist_client.h" #ifdef GSSAPI #include "ssh-gss.h" #endif #include "monitor_wrap.h" #include "digest.h" #include "kex.h" /* import */ extern ServerOptions options; extern struct sshbuf *loginmsg; /* methods */ extern Authmethod method_none; extern Authmethod method_pubkey; extern Authmethod method_passwd; extern Authmethod method_kbdint; extern Authmethod method_hostbased; #ifdef GSSAPI extern Authmethod method_gssapi; #endif Authmethod *authmethods[] = { &method_none, &method_pubkey, #ifdef GSSAPI &method_gssapi, #endif &method_passwd, &method_kbdint, &method_hostbased, NULL }; /* protocol */ static int input_service_request(int, u_int32_t, struct ssh *); static int input_userauth_request(int, u_int32_t, struct ssh *); /* helper */ static Authmethod *authmethod_byname(const char *); static Authmethod *authmethod_lookup(Authctxt *, const char *); static char *authmethods_get(Authctxt *authctxt); #define MATCH_NONE 0 /* method or submethod mismatch */ #define MATCH_METHOD 1 /* method matches (no submethod specified) */ #define MATCH_BOTH 2 /* method and submethod match */ #define MATCH_PARTIAL 3 /* method matches, submethod can't be checked */ static int list_starts_with(const char *, const char *, const char *); char * auth2_read_banner(void) { struct stat st; char *banner = NULL; size_t len, n; int fd; if ((fd = open(options.banner, O_RDONLY)) == -1) return (NULL); if (fstat(fd, &st) == -1) { close(fd); return (NULL); } if (st.st_size <= 0 || st.st_size > 1*1024*1024) { close(fd); return (NULL); } len = (size_t)st.st_size; /* truncate */ banner = xmalloc(len + 1); n = atomicio(read, fd, banner, len); close(fd); if (n != len) { free(banner); return (NULL); } banner[n] = '\0'; return (banner); } static void userauth_send_banner(struct ssh *ssh, const char *msg) { int r; if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_BANNER)) != 0 || (r = sshpkt_put_cstring(ssh, msg)) != 0 || (r = sshpkt_put_cstring(ssh, "")) != 0 || /* language, unused */ (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send packet"); debug("%s: sent", __func__); } static void userauth_banner(struct ssh *ssh) { char *banner = NULL; if (options.banner == NULL) return; if ((banner = mm_auth2_read_banner()) == NULL) goto done; userauth_send_banner(ssh, banner); done: free(banner); } /* * loop until authctxt->success == TRUE */ void do_authentication2(struct ssh *ssh) { Authctxt *authctxt = ssh->authctxt; ssh_dispatch_init(ssh, &dispatch_protocol_error); if (ssh->kex->ext_info_c) ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &kex_input_ext_info); ssh_dispatch_set(ssh, SSH2_MSG_SERVICE_REQUEST, &input_service_request); ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &authctxt->success); ssh->authctxt = NULL; } static int input_service_request(int type, u_int32_t seq, struct ssh *ssh) { Authctxt *authctxt = ssh->authctxt; char *service = NULL; int r, acceptit = 0; if ((r = sshpkt_get_cstring(ssh, &service, NULL)) != 0 || (r = sshpkt_get_end(ssh)) != 0) goto out; if (authctxt == NULL) fatal("input_service_request: no authctxt"); if (strcmp(service, "ssh-userauth") == 0) { if (!authctxt->success) { acceptit = 1; /* now we can handle user-auth requests */ ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_REQUEST, &input_userauth_request); } } /* XXX all other service requests are denied */ if (acceptit) { if ((r = sshpkt_start(ssh, SSH2_MSG_SERVICE_ACCEPT)) != 0 || (r = sshpkt_put_cstring(ssh, service)) != 0 || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) goto out; } else { debug("bad service request %s", service); ssh_packet_disconnect(ssh, "bad service request %s", service); } ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &dispatch_protocol_error); r = 0; out: free(service); return r; } #define MIN_FAIL_DELAY_SECONDS 0.005 #define MAX_FAIL_DELAY_SECONDS 5.0 static double user_specific_delay(const char *user) { char b[512]; size_t len = ssh_digest_bytes(SSH_DIGEST_SHA512); u_char *hash = xmalloc(len); double delay; (void)snprintf(b, sizeof b, "%llu%s", (unsigned long long)options.timing_secret, user); if (ssh_digest_memory(SSH_DIGEST_SHA512, b, strlen(b), hash, len) != 0) fatal_f("ssh_digest_memory"); /* 0-4.2 ms of delay */ delay = (double)PEEK_U32(hash) / 1000 / 1000 / 1000 / 1000; freezero(hash, len); debug3_f("user specific delay %0.3lfms", delay*1000); return MIN_FAIL_DELAY_SECONDS + delay; } static void ensure_minimum_time_since(double start, double seconds) { struct timespec ts; double elapsed = monotime_double() - start, req = seconds, remain; if (elapsed > MAX_FAIL_DELAY_SECONDS) { debug3_f("elapsed %0.3lfms exceeded the max delay " "requested %0.3lfms)", elapsed*1000, req*1000); return; } /* if we've already passed the requested time, scale up */ while ((remain = seconds - elapsed) < 0.0) seconds *= 2; ts.tv_sec = remain; ts.tv_nsec = (remain - ts.tv_sec) * 1000000000; debug3_f("elapsed %0.3lfms, delaying %0.3lfms (requested %0.3lfms)", elapsed*1000, remain*1000, req*1000); nanosleep(&ts, NULL); } static int input_userauth_request(int type, u_int32_t seq, struct ssh *ssh) { Authctxt *authctxt = ssh->authctxt; Authmethod *m = NULL; char *user = NULL, *service = NULL, *method = NULL, *style = NULL; int r, authenticated = 0; double tstart = monotime_double(); if (authctxt == NULL) fatal("input_userauth_request: no authctxt"); if ((r = sshpkt_get_cstring(ssh, &user, NULL)) != 0 || (r = sshpkt_get_cstring(ssh, &service, NULL)) != 0 || (r = sshpkt_get_cstring(ssh, &method, NULL)) != 0) goto out; debug("userauth-request for user %s service %s method %s", user, service, method); debug("attempt %d failures %d", authctxt->attempt, authctxt->failures); if ((style = strchr(user, ':')) != NULL) *style++ = 0; if (authctxt->attempt >= 1024) auth_maxtries_exceeded(ssh); if (authctxt->attempt++ == 0) { /* setup auth context */ authctxt->pw = mm_getpwnamallow(ssh, user); authctxt->user = xstrdup(user); if (authctxt->pw && strcmp(service, "ssh-connection")==0) { authctxt->valid = 1; debug2_f("setting up authctxt for %s", user); } else { authctxt->valid = 0; /* Invalid user, fake password information */ authctxt->pw = fakepw(); #ifdef SSH_AUDIT_EVENTS mm_audit_event(ssh, SSH_INVALID_USER); #endif } #ifdef USE_PAM if (options.use_pam) mm_start_pam(ssh); #endif ssh_packet_set_log_preamble(ssh, "%suser %s", authctxt->valid ? "authenticating " : "invalid ", user); setproctitle("%s [net]", authctxt->valid ? user : "unknown"); authctxt->service = xstrdup(service); authctxt->style = style ? xstrdup(style) : NULL; mm_inform_authserv(service, style); userauth_banner(ssh); if ((r = kex_server_update_ext_info(ssh)) != 0) fatal_fr(r, "kex_server_update_ext_info failed"); if (auth2_setup_methods_lists(authctxt) != 0) ssh_packet_disconnect(ssh, "no authentication methods enabled"); } else if (strcmp(user, authctxt->user) != 0 || strcmp(service, authctxt->service) != 0) { ssh_packet_disconnect(ssh, "Change of username or service " "not allowed: (%s,%s) -> (%s,%s)", authctxt->user, authctxt->service, user, service); } /* reset state */ auth2_challenge_stop(ssh); #ifdef GSSAPI /* XXX move to auth2_gssapi_stop() */ ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL); #endif auth2_authctxt_reset_info(authctxt); authctxt->postponed = 0; authctxt->server_caused_failure = 0; /* try to authenticate user */ m = authmethod_lookup(authctxt, method); if (m != NULL && authctxt->failures < options.max_authtries) { debug2("input_userauth_request: try method %s", method); authenticated = m->userauth(ssh, method); } if (!authctxt->authenticated && strcmp(method, "none") != 0) ensure_minimum_time_since(tstart, user_specific_delay(authctxt->user)); userauth_finish(ssh, authenticated, method, NULL); r = 0; out: free(service); free(user); free(method); return r; } void userauth_finish(struct ssh *ssh, int authenticated, const char *packet_method, const char *submethod) { Authctxt *authctxt = ssh->authctxt; Authmethod *m = NULL; const char *method = packet_method; char *methods; int r, partial = 0; if (authenticated) { if (!authctxt->valid) { fatal("INTERNAL ERROR: authenticated invalid user %s", authctxt->user); } if (authctxt->postponed) fatal("INTERNAL ERROR: authenticated and postponed"); /* prefer primary authmethod name to possible synonym */ if ((m = authmethod_byname(method)) == NULL) fatal("INTERNAL ERROR: bad method %s", method); method = m->cfg->name; } /* Special handling for root */ if (authenticated && authctxt->pw->pw_uid == 0 && !auth_root_allowed(ssh, method)) { authenticated = 0; #ifdef SSH_AUDIT_EVENTS mm_audit_event(ssh, SSH_LOGIN_ROOT_DENIED); #endif } if (authenticated && options.num_auth_methods != 0) { if (!auth2_update_methods_lists(authctxt, method, submethod)) { authenticated = 0; partial = 1; } } /* Log before sending the reply */ auth_log(ssh, authenticated, partial, method, submethod); /* Update information exposed to session */ if (authenticated || partial) auth2_update_session_info(authctxt, method, submethod); if (authctxt->postponed) return; #ifdef USE_PAM if (options.use_pam && authenticated) { int r, success = mm_do_pam_account(); /* If PAM returned a message, send it to the user. */ if (sshbuf_len(loginmsg) > 0) { if ((r = sshbuf_put(loginmsg, "\0", 1)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); userauth_send_banner(ssh, sshbuf_ptr(loginmsg)); if ((r = ssh_packet_write_wait(ssh)) != 0) { sshpkt_fatal(ssh, r, "%s: send PAM banner", __func__); } } if (!success) { fatal("Access denied for user %s by PAM account " "configuration", authctxt->user); } } #endif if (authenticated == 1) { /* turn off userauth */ ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_REQUEST, &dispatch_protocol_ignore); if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_SUCCESS)) != 0 || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) fatal_fr(r, "send success packet"); /* now we can break out */ authctxt->success = 1; ssh_packet_set_log_preamble(ssh, "user %s", authctxt->user); } else { /* Allow initial try of "none" auth without failure penalty */ if (!partial && !authctxt->server_caused_failure && - (authctxt->attempt > 1 || strcmp(method, "none") != 0)) { + (authctxt->attempt > 1 || strcmp(method, "none") != 0)) authctxt->failures++; - BLACKLIST_NOTIFY(ssh, BLACKLIST_AUTH_FAIL, "ssh"); - } if (authctxt->failures >= options.max_authtries) { #ifdef SSH_AUDIT_EVENTS mm_audit_event(ssh, SSH_LOGIN_EXCEED_MAXTRIES); #endif auth_maxtries_exceeded(ssh); } methods = authmethods_get(authctxt); debug3_f("failure partial=%d next methods=\"%s\"", partial, methods); if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_FAILURE)) != 0 || (r = sshpkt_put_cstring(ssh, methods)) != 0 || (r = sshpkt_put_u8(ssh, partial)) != 0 || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) fatal_fr(r, "send failure packet"); free(methods); } } /* * Checks whether method is allowed by at least one AuthenticationMethods * methods list. Returns 1 if allowed, or no methods lists configured. * 0 otherwise. */ int auth2_method_allowed(Authctxt *authctxt, const char *method, const char *submethod) { u_int i; /* * NB. authctxt->num_auth_methods might be zero as a result of * auth2_setup_methods_lists(), so check the configuration. */ if (options.num_auth_methods == 0) return 1; for (i = 0; i < authctxt->num_auth_methods; i++) { if (list_starts_with(authctxt->auth_methods[i], method, submethod) != MATCH_NONE) return 1; } return 0; } static char * authmethods_get(Authctxt *authctxt) { struct sshbuf *b; char *list; int i, r; if ((b = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); for (i = 0; authmethods[i] != NULL; i++) { if (strcmp(authmethods[i]->cfg->name, "none") == 0) continue; if (authmethods[i]->cfg->enabled == NULL || *(authmethods[i]->cfg->enabled) == 0) continue; if (!auth2_method_allowed(authctxt, authmethods[i]->cfg->name, NULL)) continue; if ((r = sshbuf_putf(b, "%s%s", sshbuf_len(b) ? "," : "", authmethods[i]->cfg->name)) != 0) fatal_fr(r, "buffer error"); } if ((list = sshbuf_dup_string(b)) == NULL) fatal_f("sshbuf_dup_string failed"); sshbuf_free(b); return list; } static Authmethod * authmethod_byname(const char *name) { int i; if (name == NULL) fatal_f("NULL authentication method name"); for (i = 0; authmethods[i] != NULL; i++) { if (strcmp(name, authmethods[i]->cfg->name) == 0 || (authmethods[i]->cfg->synonym != NULL && strcmp(name, authmethods[i]->cfg->synonym) == 0)) return authmethods[i]; } debug_f("unrecognized authentication method name: %s", name); return NULL; } static Authmethod * authmethod_lookup(Authctxt *authctxt, const char *name) { Authmethod *method; if ((method = authmethod_byname(name)) == NULL) return NULL; if (method->cfg->enabled == NULL || *(method->cfg->enabled) == 0) { debug3_f("method %s not enabled", name); return NULL; } if (!auth2_method_allowed(authctxt, method->cfg->name, NULL)) { debug3_f("method %s not allowed " "by AuthenticationMethods", name); return NULL; } return method; } /* * Prune the AuthenticationMethods supplied in the configuration, removing * any methods lists that include disabled methods. Note that this might * leave authctxt->num_auth_methods == 0, even when multiple required auth * has been requested. For this reason, all tests for whether multiple is * enabled should consult options.num_auth_methods directly. */ int auth2_setup_methods_lists(Authctxt *authctxt) { u_int i; /* First, normalise away the "any" pseudo-method */ if (options.num_auth_methods == 1 && strcmp(options.auth_methods[0], "any") == 0) { free(options.auth_methods[0]); options.auth_methods[0] = NULL; options.num_auth_methods = 0; } if (options.num_auth_methods == 0) return 0; debug3_f("checking methods"); authctxt->auth_methods = xcalloc(options.num_auth_methods, sizeof(*authctxt->auth_methods)); authctxt->num_auth_methods = 0; for (i = 0; i < options.num_auth_methods; i++) { if (auth2_methods_valid(options.auth_methods[i], 1) != 0) { logit("Authentication methods list \"%s\" contains " "disabled method, skipping", options.auth_methods[i]); continue; } debug("authentication methods list %d: %s", authctxt->num_auth_methods, options.auth_methods[i]); authctxt->auth_methods[authctxt->num_auth_methods++] = xstrdup(options.auth_methods[i]); } if (authctxt->num_auth_methods == 0) { error("No AuthenticationMethods left after eliminating " "disabled methods"); return -1; } return 0; } static int list_starts_with(const char *methods, const char *method, const char *submethod) { size_t l = strlen(method); int match; const char *p; if (strncmp(methods, method, l) != 0) return MATCH_NONE; p = methods + l; match = MATCH_METHOD; if (*p == ':') { if (!submethod) return MATCH_PARTIAL; l = strlen(submethod); p += 1; if (strncmp(submethod, p, l)) return MATCH_NONE; p += l; match = MATCH_BOTH; } if (*p != ',' && *p != '\0') return MATCH_NONE; return match; } /* * Remove method from the start of a comma-separated list of methods. * Returns 0 if the list of methods did not start with that method or 1 * if it did. */ static int remove_method(char **methods, const char *method, const char *submethod) { char *omethods = *methods, *p; size_t l = strlen(method); int match; match = list_starts_with(omethods, method, submethod); if (match != MATCH_METHOD && match != MATCH_BOTH) return 0; p = omethods + l; if (submethod && match == MATCH_BOTH) p += 1 + strlen(submethod); /* include colon */ if (*p == ',') p++; *methods = xstrdup(p); free(omethods); return 1; } /* * Called after successful authentication. Will remove the successful method * from the start of each list in which it occurs. If it was the last method * in any list, then authentication is deemed successful. * Returns 1 if the method completed any authentication list or 0 otherwise. */ int auth2_update_methods_lists(Authctxt *authctxt, const char *method, const char *submethod) { u_int i, found = 0; debug3_f("updating methods list after \"%s\"", method); for (i = 0; i < authctxt->num_auth_methods; i++) { if (!remove_method(&(authctxt->auth_methods[i]), method, submethod)) continue; found = 1; if (*authctxt->auth_methods[i] == '\0') { debug2("authentication methods list %d complete", i); return 1; } debug3("authentication methods list %d remaining: \"%s\"", i, authctxt->auth_methods[i]); } /* This should not happen, but would be bad if it did */ if (!found) fatal_f("method not in AuthenticationMethods"); return 0; } /* Reset method-specific information */ void auth2_authctxt_reset_info(Authctxt *authctxt) { sshkey_free(authctxt->auth_method_key); free(authctxt->auth_method_info); authctxt->auth_method_key = NULL; authctxt->auth_method_info = NULL; } /* Record auth method-specific information for logs */ void auth2_record_info(Authctxt *authctxt, const char *fmt, ...) { va_list ap; int i; free(authctxt->auth_method_info); authctxt->auth_method_info = NULL; va_start(ap, fmt); i = vasprintf(&authctxt->auth_method_info, fmt, ap); va_end(ap); if (i == -1) fatal_f("vasprintf failed"); } /* * Records a public key used in authentication. This is used for logging * and to ensure that the same key is not subsequently accepted again for * multiple authentication. */ void auth2_record_key(Authctxt *authctxt, int authenticated, const struct sshkey *key) { struct sshkey **tmp, *dup; int r; if ((r = sshkey_from_private(key, &dup)) != 0) fatal_fr(r, "copy key"); sshkey_free(authctxt->auth_method_key); authctxt->auth_method_key = dup; if (!authenticated) return; /* If authenticated, make sure we don't accept this key again */ if ((r = sshkey_from_private(key, &dup)) != 0) fatal_fr(r, "copy key"); if (authctxt->nprev_keys >= INT_MAX || (tmp = recallocarray(authctxt->prev_keys, authctxt->nprev_keys, authctxt->nprev_keys + 1, sizeof(*authctxt->prev_keys))) == NULL) fatal_f("reallocarray failed"); authctxt->prev_keys = tmp; authctxt->prev_keys[authctxt->nprev_keys] = dup; authctxt->nprev_keys++; } /* Checks whether a key has already been previously used for authentication */ int auth2_key_already_used(Authctxt *authctxt, const struct sshkey *key) { u_int i; char *fp; for (i = 0; i < authctxt->nprev_keys; i++) { if (sshkey_equal_public(key, authctxt->prev_keys[i])) { fp = sshkey_fingerprint(authctxt->prev_keys[i], options.fingerprint_hash, SSH_FP_DEFAULT); debug3_f("key already used: %s %s", sshkey_type(authctxt->prev_keys[i]), fp == NULL ? "UNKNOWN" : fp); free(fp); return 1; } } return 0; } /* * Updates authctxt->session_info with details of authentication. Should be * whenever an authentication method succeeds. */ void auth2_update_session_info(Authctxt *authctxt, const char *method, const char *submethod) { int r; if (authctxt->session_info == NULL) { if ((authctxt->session_info = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); } /* Append method[/submethod] */ if ((r = sshbuf_putf(authctxt->session_info, "%s%s%s", method, submethod == NULL ? "" : "/", submethod == NULL ? "" : submethod)) != 0) fatal_fr(r, "append method"); /* Append key if present */ if (authctxt->auth_method_key != NULL) { if ((r = sshbuf_put_u8(authctxt->session_info, ' ')) != 0 || (r = sshkey_format_text(authctxt->auth_method_key, authctxt->session_info)) != 0) fatal_fr(r, "append key"); } if (authctxt->auth_method_info != NULL) { /* Ensure no ambiguity here */ if (strchr(authctxt->auth_method_info, '\n') != NULL) fatal_f("auth_method_info contains \\n"); if ((r = sshbuf_put_u8(authctxt->session_info, ' ')) != 0 || (r = sshbuf_putf(authctxt->session_info, "%s", authctxt->auth_method_info)) != 0) { fatal_fr(r, "append method info"); } } if ((r = sshbuf_put_u8(authctxt->session_info, '\n')) != 0) fatal_fr(r, "append"); } diff --git a/crypto/openssh/monitor.c b/crypto/openssh/monitor.c index 2179553d3401..b826ecdb9065 100644 --- a/crypto/openssh/monitor.c +++ b/crypto/openssh/monitor.c @@ -1,2067 +1,2077 @@ /* $OpenBSD: monitor.c,v 1.247 2024/12/03 22:30:03 jsg Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #include #include #ifdef HAVE_PATHS_H #include #endif #include #include #ifdef HAVE_STDINT_H # include #endif #include #include #include #include #include #ifdef HAVE_POLL_H #include #else # ifdef HAVE_SYS_POLL_H # include # endif #endif #ifdef WITH_OPENSSL #include #endif #include "openbsd-compat/sys-tree.h" #include "openbsd-compat/sys-queue.h" #include "openbsd-compat/openssl-compat.h" #include "atomicio.h" #include "xmalloc.h" #include "ssh.h" #include "sshkey.h" #include "sshbuf.h" #include "hostfile.h" #include "auth.h" #include "cipher.h" #include "kex.h" #include "dh.h" #include "auth-pam.h" #include "packet.h" #include "auth-options.h" #include "sshpty.h" #include "channels.h" #include "session.h" #include "sshlogin.h" #include "canohost.h" #include "log.h" #include "misc.h" #include "servconf.h" #include "monitor.h" +#include "blacklist_client.h" + #ifdef GSSAPI #include "ssh-gss.h" #endif #include "monitor_wrap.h" #include "monitor_fdpass.h" #include "compat.h" #include "ssh2.h" #include "authfd.h" #include "match.h" #include "ssherr.h" #include "sk-api.h" #include "srclimit.h" #ifdef GSSAPI static Gssctxt *gsscontext = NULL; #endif /* Imports */ extern ServerOptions options; extern u_int utmp_len; extern struct sshbuf *cfg; extern struct sshbuf *loginmsg; extern struct include_list includes; extern struct sshauthopt *auth_opts; /* XXX move to permanent ssh->authctxt? */ /* State exported from the child */ static struct sshbuf *child_state; /* Functions on the monitor that answer unprivileged requests */ int mm_answer_moduli(struct ssh *, int, struct sshbuf *); int mm_answer_sign(struct ssh *, int, struct sshbuf *); int mm_answer_pwnamallow(struct ssh *, int, struct sshbuf *); int mm_answer_auth2_read_banner(struct ssh *, int, struct sshbuf *); int mm_answer_authserv(struct ssh *, int, struct sshbuf *); int mm_answer_authpassword(struct ssh *, int, struct sshbuf *); int mm_answer_bsdauthquery(struct ssh *, int, struct sshbuf *); int mm_answer_bsdauthrespond(struct ssh *, int, struct sshbuf *); int mm_answer_keyallowed(struct ssh *, int, struct sshbuf *); int mm_answer_keyverify(struct ssh *, int, struct sshbuf *); int mm_answer_pty(struct ssh *, int, struct sshbuf *); int mm_answer_pty_cleanup(struct ssh *, int, struct sshbuf *); int mm_answer_term(struct ssh *, int, struct sshbuf *); int mm_answer_state(struct ssh *, int, struct sshbuf *); #ifdef USE_PAM int mm_answer_pam_start(struct ssh *, int, struct sshbuf *); int mm_answer_pam_account(struct ssh *, int, struct sshbuf *); int mm_answer_pam_init_ctx(struct ssh *, int, struct sshbuf *); int mm_answer_pam_query(struct ssh *, int, struct sshbuf *); int mm_answer_pam_respond(struct ssh *, int, struct sshbuf *); int mm_answer_pam_free_ctx(struct ssh *, int, struct sshbuf *); #endif #ifdef GSSAPI int mm_answer_gss_setup_ctx(struct ssh *, int, struct sshbuf *); int mm_answer_gss_accept_ctx(struct ssh *, int, struct sshbuf *); int mm_answer_gss_userok(struct ssh *, int, struct sshbuf *); int mm_answer_gss_checkmic(struct ssh *, int, struct sshbuf *); #endif #ifdef SSH_AUDIT_EVENTS int mm_answer_audit_event(struct ssh *, int, struct sshbuf *); int mm_answer_audit_command(struct ssh *, int, struct sshbuf *); #endif static Authctxt *authctxt; /* local state for key verify */ static u_char *key_blob = NULL; static size_t key_bloblen = 0; static u_int key_blobtype = MM_NOKEY; static struct sshauthopt *key_opts = NULL; static char *hostbased_cuser = NULL; static char *hostbased_chost = NULL; static char *auth_method = "unknown"; static char *auth_submethod = NULL; static u_int session_id2_len = 0; static u_char *session_id2 = NULL; static pid_t monitor_child_pid; int auth_attempted = 0; struct mon_table { enum monitor_reqtype type; int flags; int (*f)(struct ssh *, int, struct sshbuf *); }; #define MON_ISAUTH 0x0004 /* Required for Authentication */ #define MON_AUTHDECIDE 0x0008 /* Decides Authentication */ #define MON_ONCE 0x0010 /* Disable after calling */ #define MON_ALOG 0x0020 /* Log auth attempt without authenticating */ #define MON_AUTH (MON_ISAUTH|MON_AUTHDECIDE) #define MON_PERMIT 0x1000 /* Request is permitted */ static int monitor_read(struct ssh *, struct monitor *, struct mon_table *, struct mon_table **); static int monitor_read_log(struct monitor *); struct mon_table mon_dispatch_proto20[] = { {MONITOR_REQ_STATE, MON_ONCE, mm_answer_state}, #ifdef WITH_OPENSSL {MONITOR_REQ_MODULI, MON_ONCE, mm_answer_moduli}, #endif {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign}, {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow}, {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv}, {MONITOR_REQ_AUTH2_READ_BANNER, MON_ONCE, mm_answer_auth2_read_banner}, {MONITOR_REQ_AUTHPASSWORD, MON_AUTH, mm_answer_authpassword}, #ifdef USE_PAM {MONITOR_REQ_PAM_START, MON_ONCE, mm_answer_pam_start}, {MONITOR_REQ_PAM_ACCOUNT, 0, mm_answer_pam_account}, {MONITOR_REQ_PAM_INIT_CTX, MON_ONCE, mm_answer_pam_init_ctx}, {MONITOR_REQ_PAM_QUERY, 0, mm_answer_pam_query}, {MONITOR_REQ_PAM_RESPOND, MON_ONCE, mm_answer_pam_respond}, {MONITOR_REQ_PAM_FREE_CTX, MON_ONCE|MON_AUTHDECIDE, mm_answer_pam_free_ctx}, #endif #ifdef SSH_AUDIT_EVENTS {MONITOR_REQ_AUDIT_EVENT, MON_PERMIT, mm_answer_audit_event}, #endif #ifdef BSD_AUTH {MONITOR_REQ_BSDAUTHQUERY, MON_ISAUTH, mm_answer_bsdauthquery}, {MONITOR_REQ_BSDAUTHRESPOND, MON_AUTH, mm_answer_bsdauthrespond}, #endif {MONITOR_REQ_KEYALLOWED, MON_ISAUTH, mm_answer_keyallowed}, {MONITOR_REQ_KEYVERIFY, MON_AUTH, mm_answer_keyverify}, #ifdef GSSAPI {MONITOR_REQ_GSSSETUP, MON_ISAUTH, mm_answer_gss_setup_ctx}, {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx}, {MONITOR_REQ_GSSUSEROK, MON_ONCE|MON_AUTHDECIDE, mm_answer_gss_userok}, {MONITOR_REQ_GSSCHECKMIC, MON_ONCE, mm_answer_gss_checkmic}, #endif {0, 0, NULL} }; struct mon_table mon_dispatch_postauth20[] = { {MONITOR_REQ_STATE, MON_ONCE, mm_answer_state}, #ifdef WITH_OPENSSL {MONITOR_REQ_MODULI, 0, mm_answer_moduli}, #endif {MONITOR_REQ_SIGN, 0, mm_answer_sign}, {MONITOR_REQ_PTY, 0, mm_answer_pty}, {MONITOR_REQ_PTYCLEANUP, 0, mm_answer_pty_cleanup}, {MONITOR_REQ_TERM, 0, mm_answer_term}, #ifdef SSH_AUDIT_EVENTS {MONITOR_REQ_AUDIT_EVENT, MON_PERMIT, mm_answer_audit_event}, {MONITOR_REQ_AUDIT_COMMAND, MON_PERMIT, mm_answer_audit_command}, #endif {0, 0, NULL} }; struct mon_table *mon_dispatch; /* Specifies if a certain message is allowed at the moment */ static void monitor_permit(struct mon_table *ent, enum monitor_reqtype type, int permit) { while (ent->f != NULL) { if (ent->type == type) { ent->flags &= ~MON_PERMIT; ent->flags |= permit ? MON_PERMIT : 0; return; } ent++; } } static void monitor_permit_authentications(int permit) { struct mon_table *ent = mon_dispatch; while (ent->f != NULL) { if (ent->flags & MON_AUTH) { ent->flags &= ~MON_PERMIT; ent->flags |= permit ? MON_PERMIT : 0; } ent++; } } void monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor) { struct mon_table *ent; int authenticated = 0, partial = 0; debug3("preauth child monitor started"); if (pmonitor->m_recvfd >= 0) close(pmonitor->m_recvfd); if (pmonitor->m_log_sendfd >= 0) close(pmonitor->m_log_sendfd); pmonitor->m_log_sendfd = pmonitor->m_recvfd = -1; authctxt = (Authctxt *)ssh->authctxt; memset(authctxt, 0, sizeof(*authctxt)); ssh->authctxt = authctxt; authctxt->loginmsg = loginmsg; mon_dispatch = mon_dispatch_proto20; /* Permit requests for state, moduli and signatures */ monitor_permit(mon_dispatch, MONITOR_REQ_STATE, 1); monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); /* The first few requests do not require asynchronous access */ while (!authenticated) { partial = 0; auth_method = "unknown"; auth_submethod = NULL; auth2_authctxt_reset_info(authctxt); authenticated = (monitor_read(ssh, pmonitor, mon_dispatch, &ent) == 1); /* Record that auth was attempted to set exit status later */ if ((ent->flags & MON_AUTH) != 0) auth_attempted = 1; /* Special handling for multiple required authentications */ if (options.num_auth_methods != 0) { if (authenticated && !auth2_update_methods_lists(authctxt, auth_method, auth_submethod)) { debug3_f("method %s: partial", auth_method); authenticated = 0; partial = 1; } } if (authenticated) { if (!(ent->flags & MON_AUTHDECIDE)) fatal_f("unexpected authentication from %d", ent->type); if (authctxt->pw->pw_uid == 0 && !auth_root_allowed(ssh, auth_method)) authenticated = 0; #ifdef USE_PAM /* PAM needs to perform account checks after auth */ if (options.use_pam && authenticated) { struct sshbuf *m; if ((m = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); mm_request_receive_expect(pmonitor->m_sendfd, MONITOR_REQ_PAM_ACCOUNT, m); authenticated = mm_answer_pam_account( ssh, pmonitor->m_sendfd, m); sshbuf_free(m); } #endif } if (ent->flags & (MON_AUTHDECIDE|MON_ALOG)) { auth_log(ssh, authenticated, partial, auth_method, auth_submethod); if (!partial && !authenticated) authctxt->failures++; if (authenticated || partial) { auth2_update_session_info(authctxt, auth_method, auth_submethod); } } if (authctxt->failures > options.max_authtries) { + BLACKLIST_NOTIFY(ssh, BLACKLIST_AUTH_FAIL, + "Too many authentication attempts"); /* Shouldn't happen */ fatal_f("privsep child made too many authentication " "attempts"); } } - if (!authctxt->valid) + if (!authctxt->valid) { + BLACKLIST_NOTIFY(ssh, BLACKLIST_AUTH_FAIL, + "Authenticated invalid user"); fatal_f("authenticated invalid user"); - if (strcmp(auth_method, "unknown") == 0) + } + if (strcmp(auth_method, "unknown") == 0) { + BLACKLIST_NOTIFY(ssh, BLACKLIST_AUTH_FAIL, + "Authentication method name unknown"); fatal_f("authentication method name unknown"); + } debug_f("user %s authenticated by privileged process", authctxt->user); auth_attempted = 0; ssh->authctxt = NULL; ssh_packet_set_log_preamble(ssh, "user %s", authctxt->user); mm_get_keystate(ssh, pmonitor); /* Drain any buffered messages from the child */ while (pmonitor->m_log_recvfd != -1 && monitor_read_log(pmonitor) == 0) ; if (pmonitor->m_recvfd >= 0) close(pmonitor->m_recvfd); if (pmonitor->m_log_sendfd >= 0) close(pmonitor->m_log_sendfd); pmonitor->m_sendfd = pmonitor->m_log_recvfd = -1; } static void monitor_set_child_handler(pid_t pid) { monitor_child_pid = pid; } static void monitor_child_handler(int sig) { kill(monitor_child_pid, sig); } void monitor_child_postauth(struct ssh *ssh, struct monitor *pmonitor) { close(pmonitor->m_recvfd); pmonitor->m_recvfd = -1; monitor_set_child_handler(pmonitor->m_pid); ssh_signal(SIGHUP, &monitor_child_handler); ssh_signal(SIGTERM, &monitor_child_handler); ssh_signal(SIGINT, &monitor_child_handler); #ifdef SIGXFSZ ssh_signal(SIGXFSZ, SIG_IGN); #endif mon_dispatch = mon_dispatch_postauth20; /* Permit requests for moduli and signatures */ monitor_permit(mon_dispatch, MONITOR_REQ_STATE, 1); monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); if (auth_opts->permit_pty_flag) { monitor_permit(mon_dispatch, MONITOR_REQ_PTY, 1); monitor_permit(mon_dispatch, MONITOR_REQ_PTYCLEANUP, 1); } for (;;) monitor_read(ssh, pmonitor, mon_dispatch, NULL); } static int monitor_read_log(struct monitor *pmonitor) { struct sshbuf *logmsg; u_int len, level, forced; char *msg; u_char *p; int r; if ((logmsg = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); /* Read length */ if ((r = sshbuf_reserve(logmsg, 4, &p)) != 0) fatal_fr(r, "reserve len"); if (atomicio(read, pmonitor->m_log_recvfd, p, 4) != 4) { if (errno == EPIPE) { sshbuf_free(logmsg); debug_f("child log fd closed"); close(pmonitor->m_log_recvfd); pmonitor->m_log_recvfd = -1; return -1; } fatal_f("log fd read: %s", strerror(errno)); } if ((r = sshbuf_get_u32(logmsg, &len)) != 0) fatal_fr(r, "parse len"); if (len <= 4 || len > 8192) fatal_f("invalid log message length %u", len); /* Read severity, message */ sshbuf_reset(logmsg); if ((r = sshbuf_reserve(logmsg, len, &p)) != 0) fatal_fr(r, "reserve msg"); if (atomicio(read, pmonitor->m_log_recvfd, p, len) != len) fatal_f("log fd read: %s", strerror(errno)); if ((r = sshbuf_get_u32(logmsg, &level)) != 0 || (r = sshbuf_get_u32(logmsg, &forced)) != 0 || (r = sshbuf_get_cstring(logmsg, &msg, NULL)) != 0) fatal_fr(r, "parse"); /* Log it */ if (log_level_name(level) == NULL) fatal_f("invalid log level %u (corrupted message?)", level); sshlogdirect(level, forced, "%s [%s]", msg, mon_dispatch == mon_dispatch_postauth20 ? "postauth" : "preauth"); sshbuf_free(logmsg); free(msg); return 0; } static int monitor_read(struct ssh *ssh, struct monitor *pmonitor, struct mon_table *ent, struct mon_table **pent) { struct sshbuf *m; int r, ret; u_char type; struct pollfd pfd[2]; for (;;) { memset(&pfd, 0, sizeof(pfd)); pfd[0].fd = pmonitor->m_sendfd; pfd[0].events = POLLIN; pfd[1].fd = pmonitor->m_log_recvfd; pfd[1].events = pfd[1].fd == -1 ? 0 : POLLIN; if (poll(pfd, pfd[1].fd == -1 ? 1 : 2, -1) == -1) { if (errno == EINTR || errno == EAGAIN) continue; fatal_f("poll: %s", strerror(errno)); } if (pfd[1].revents) { /* * Drain all log messages before processing next * monitor request. */ monitor_read_log(pmonitor); continue; } if (pfd[0].revents) break; /* Continues below */ } if ((m = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); mm_request_receive(pmonitor->m_sendfd, m); if ((r = sshbuf_get_u8(m, &type)) != 0) fatal_fr(r, "parse type"); debug3_f("checking request %d", type); while (ent->f != NULL) { if (ent->type == type) break; ent++; } if (ent->f != NULL) { if (!(ent->flags & MON_PERMIT)) fatal_f("unpermitted request %d", type); ret = (*ent->f)(ssh, pmonitor->m_sendfd, m); sshbuf_free(m); /* The child may use this request only once, disable it */ if (ent->flags & MON_ONCE) { debug2_f("%d used once, disabling now", type); ent->flags &= ~MON_PERMIT; } if (pent != NULL) *pent = ent; return ret; } fatal_f("unsupported request: %d", type); /* NOTREACHED */ return (-1); } /* allowed key state */ static int monitor_allowed_key(const u_char *blob, u_int bloblen) { /* make sure key is allowed */ if (key_blob == NULL || key_bloblen != bloblen || timingsafe_bcmp(key_blob, blob, key_bloblen)) return (0); return (1); } static void monitor_reset_key_state(void) { /* reset state */ free(key_blob); free(hostbased_cuser); free(hostbased_chost); sshauthopt_free(key_opts); key_blob = NULL; key_bloblen = 0; key_blobtype = MM_NOKEY; key_opts = NULL; hostbased_cuser = NULL; hostbased_chost = NULL; } int mm_answer_state(struct ssh *ssh, int sock, struct sshbuf *m) { struct sshbuf *inc = NULL, *hostkeys = NULL; struct sshbuf *opts = NULL, *confdata = NULL; struct include_item *item = NULL; int postauth; int r; sshbuf_reset(m); debug_f("config len %zu", sshbuf_len(cfg)); if ((m = sshbuf_new()) == NULL || (inc = sshbuf_new()) == NULL || (opts = sshbuf_new()) == NULL || (confdata = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); /* XXX unnecessary? */ /* pack includes into a string */ TAILQ_FOREACH(item, &includes, entry) { if ((r = sshbuf_put_cstring(inc, item->selector)) != 0 || (r = sshbuf_put_cstring(inc, item->filename)) != 0 || (r = sshbuf_put_stringb(inc, item->contents)) != 0) fatal_fr(r, "compose includes"); } hostkeys = pack_hostkeys(); /* * Protocol from monitor to unpriv privsep process: * string configuration * uint64 timing_secret XXX move delays to monitor and remove * string host_keys[] { * string public_key * string certificate * } * string server_banner * string client_banner * string included_files[] { * string selector * string filename * string contents * } * string configuration_data (postauth) * string keystate (postauth) * string authenticated_user (postauth) * string session_info (postauth) * string authopts (postauth) */ if ((r = sshbuf_put_stringb(m, cfg)) != 0 || (r = sshbuf_put_u64(m, options.timing_secret)) != 0 || (r = sshbuf_put_stringb(m, hostkeys)) != 0 || (r = sshbuf_put_stringb(m, ssh->kex->server_version)) != 0 || (r = sshbuf_put_stringb(m, ssh->kex->client_version)) != 0 || (r = sshbuf_put_stringb(m, inc)) != 0) fatal_fr(r, "compose config"); postauth = (authctxt && authctxt->pw && authctxt->authenticated); if (postauth) { /* XXX shouldn't be reachable */ fatal_f("internal error: called in postauth"); } sshbuf_free(inc); sshbuf_free(opts); sshbuf_free(confdata); mm_request_send(sock, MONITOR_ANS_STATE, m); debug3_f("done"); return (0); } #ifdef WITH_OPENSSL int mm_answer_moduli(struct ssh *ssh, int sock, struct sshbuf *m) { DH *dh; const BIGNUM *dh_p, *dh_g; int r; u_int min, want, max; if ((r = sshbuf_get_u32(m, &min)) != 0 || (r = sshbuf_get_u32(m, &want)) != 0 || (r = sshbuf_get_u32(m, &max)) != 0) fatal_fr(r, "parse"); debug3_f("got parameters: %d %d %d", min, want, max); /* We need to check here, too, in case the child got corrupted */ if (max < min || want < min || max < want) fatal_f("bad parameters: %d %d %d", min, want, max); sshbuf_reset(m); dh = choose_dh(min, want, max); if (dh == NULL) { if ((r = sshbuf_put_u8(m, 0)) != 0) fatal_fr(r, "assemble empty"); return (0); } else { /* Send first bignum */ DH_get0_pqg(dh, &dh_p, NULL, &dh_g); if ((r = sshbuf_put_u8(m, 1)) != 0 || (r = sshbuf_put_bignum2(m, dh_p)) != 0 || (r = sshbuf_put_bignum2(m, dh_g)) != 0) fatal_fr(r, "assemble"); DH_free(dh); } mm_request_send(sock, MONITOR_ANS_MODULI, m); return (0); } #endif int mm_answer_sign(struct ssh *ssh, int sock, struct sshbuf *m) { extern int auth_sock; /* XXX move to state struct? */ struct sshkey *pubkey, *key; struct sshbuf *sigbuf = NULL; u_char *p = NULL, *signature = NULL; char *alg = NULL; size_t datlen, siglen; int r, is_proof = 0, keyid; u_int compat; const char proof_req[] = "hostkeys-prove-00@openssh.com"; debug3_f("entering"); if ((r = sshkey_froms(m, &pubkey)) != 0 || (r = sshbuf_get_string(m, &p, &datlen)) != 0 || (r = sshbuf_get_cstring(m, &alg, NULL)) != 0 || (r = sshbuf_get_u32(m, &compat)) != 0) fatal_fr(r, "parse"); if ((keyid = get_hostkey_index(pubkey, 1, ssh)) == -1) fatal_f("unknown hostkey"); debug_f("hostkey %s index %d", sshkey_ssh_name(pubkey), keyid); sshkey_free(pubkey); /* * Supported KEX types use SHA1 (20 bytes), SHA256 (32 bytes), * SHA384 (48 bytes) and SHA512 (64 bytes). * * Otherwise, verify the signature request is for a hostkey * proof. * * XXX perform similar check for KEX signature requests too? * it's not trivial, since what is signed is the hash, rather * than the full kex structure... */ if (datlen != 20 && datlen != 32 && datlen != 48 && datlen != 64) { /* * Construct expected hostkey proof and compare it to what * the client sent us. */ if (session_id2_len == 0) /* hostkeys is never first */ fatal_f("bad data length: %zu", datlen); if ((key = get_hostkey_public_by_index(keyid, ssh)) == NULL) fatal_f("no hostkey for index %d", keyid); if ((sigbuf = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); if ((r = sshbuf_put_cstring(sigbuf, proof_req)) != 0 || (r = sshbuf_put_string(sigbuf, session_id2, session_id2_len)) != 0 || (r = sshkey_puts(key, sigbuf)) != 0) fatal_fr(r, "assemble private key proof"); if (datlen != sshbuf_len(sigbuf) || memcmp(p, sshbuf_ptr(sigbuf), sshbuf_len(sigbuf)) != 0) fatal_f("bad data length: %zu, hostkey proof len %zu", datlen, sshbuf_len(sigbuf)); sshbuf_free(sigbuf); is_proof = 1; } /* save session id, it will be passed on the first call */ if (session_id2_len == 0) { session_id2_len = datlen; session_id2 = xmalloc(session_id2_len); memcpy(session_id2, p, session_id2_len); } if ((key = get_hostkey_by_index(keyid)) != NULL) { if ((r = sshkey_sign(key, &signature, &siglen, p, datlen, alg, options.sk_provider, NULL, compat)) != 0) fatal_fr(r, "sign"); } else if ((key = get_hostkey_public_by_index(keyid, ssh)) != NULL && auth_sock > 0) { if ((r = ssh_agent_sign(auth_sock, key, &signature, &siglen, p, datlen, alg, compat)) != 0) fatal_fr(r, "agent sign"); } else fatal_f("no hostkey from index %d", keyid); debug3_f("%s %s signature len=%zu", alg, is_proof ? "hostkey proof" : "KEX", siglen); sshbuf_reset(m); if ((r = sshbuf_put_string(m, signature, siglen)) != 0) fatal_fr(r, "assemble"); free(alg); free(p); free(signature); mm_request_send(sock, MONITOR_ANS_SIGN, m); /* Turn on permissions for getpwnam */ monitor_permit(mon_dispatch, MONITOR_REQ_PWNAM, 1); return (0); } #define PUTPW(b, id) \ do { \ if ((r = sshbuf_put_string(b, \ &pwent->id, sizeof(pwent->id))) != 0) \ fatal_fr(r, "assemble %s", #id); \ } while (0) void mm_encode_server_options(struct sshbuf *m) { int r; u_int i; /* XXX this leaks raw pointers to the unpriv child processes */ if ((r = sshbuf_put_string(m, &options, sizeof(options))) != 0) fatal_fr(r, "assemble options"); #define M_CP_STROPT(x) do { \ if (options.x != NULL && \ (r = sshbuf_put_cstring(m, options.x)) != 0) \ fatal_fr(r, "assemble %s", #x); \ } while (0) #define M_CP_STRARRAYOPT(x, nx) do { \ for (i = 0; i < options.nx; i++) { \ if ((r = sshbuf_put_cstring(m, options.x[i])) != 0) \ fatal_fr(r, "assemble %s", #x); \ } \ } while (0) /* See comment in servconf.h */ COPY_MATCH_STRING_OPTS(); #undef M_CP_STROPT #undef M_CP_STRARRAYOPT } /* Retrieves the password entry and also checks if the user is permitted */ int mm_answer_pwnamallow(struct ssh *ssh, int sock, struct sshbuf *m) { struct passwd *pwent; int r, allowed = 0; debug3_f("entering"); if (authctxt->attempt++ != 0) fatal_f("multiple attempts for getpwnam"); if ((r = sshbuf_get_cstring(m, &authctxt->user, NULL)) != 0) fatal_fr(r, "parse"); pwent = getpwnamallow(ssh, authctxt->user); setproctitle("%s [priv]", pwent ? authctxt->user : "unknown"); sshbuf_reset(m); if (pwent == NULL) { if ((r = sshbuf_put_u8(m, 0)) != 0) fatal_fr(r, "assemble fakepw"); authctxt->pw = fakepw(); goto out; } allowed = 1; authctxt->pw = pwent; authctxt->valid = 1; /* XXX send fake class/dir/shell, etc. */ if ((r = sshbuf_put_u8(m, 1)) != 0) fatal_fr(r, "assemble ok"); PUTPW(m, pw_uid); PUTPW(m, pw_gid); #ifdef HAVE_STRUCT_PASSWD_PW_CHANGE PUTPW(m, pw_change); #endif #ifdef HAVE_STRUCT_PASSWD_PW_EXPIRE PUTPW(m, pw_expire); #endif if ((r = sshbuf_put_cstring(m, pwent->pw_name)) != 0 || (r = sshbuf_put_cstring(m, "*")) != 0 || #ifdef HAVE_STRUCT_PASSWD_PW_GECOS (r = sshbuf_put_cstring(m, pwent->pw_gecos)) != 0 || #endif #ifdef HAVE_STRUCT_PASSWD_PW_CLASS (r = sshbuf_put_cstring(m, pwent->pw_class)) != 0 || #endif (r = sshbuf_put_cstring(m, pwent->pw_dir)) != 0 || (r = sshbuf_put_cstring(m, pwent->pw_shell)) != 0) fatal_fr(r, "assemble pw"); out: ssh_packet_set_log_preamble(ssh, "%suser %s", authctxt->valid ? "authenticating" : "invalid ", authctxt->user); if (options.refuse_connection) { logit("administratively prohibited connection for " "%s%s from %.128s port %d", authctxt->valid ? "" : "invalid user ", authctxt->user, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); cleanup_exit(EXIT_CONFIG_REFUSED); } /* Send active options to unpriv */ mm_encode_server_options(m); /* Create valid auth method lists */ if (auth2_setup_methods_lists(authctxt) != 0) { /* * The monitor will continue long enough to let the child * run to its packet_disconnect(), but it must not allow any * authentication to succeed. */ debug_f("no valid authentication method lists"); } debug3_f("sending MONITOR_ANS_PWNAM: %d", allowed); mm_request_send(sock, MONITOR_ANS_PWNAM, m); /* Allow service/style information on the auth context */ monitor_permit(mon_dispatch, MONITOR_REQ_AUTHSERV, 1); monitor_permit(mon_dispatch, MONITOR_REQ_AUTH2_READ_BANNER, 1); #ifdef USE_PAM if (options.use_pam) monitor_permit(mon_dispatch, MONITOR_REQ_PAM_START, 1); #endif return (0); } int mm_answer_auth2_read_banner(struct ssh *ssh, int sock, struct sshbuf *m) { char *banner; int r; sshbuf_reset(m); banner = auth2_read_banner(); if ((r = sshbuf_put_cstring(m, banner != NULL ? banner : "")) != 0) fatal_fr(r, "assemble"); mm_request_send(sock, MONITOR_ANS_AUTH2_READ_BANNER, m); free(banner); return (0); } int mm_answer_authserv(struct ssh *ssh, int sock, struct sshbuf *m) { int r; monitor_permit_authentications(1); if ((r = sshbuf_get_cstring(m, &authctxt->service, NULL)) != 0 || (r = sshbuf_get_cstring(m, &authctxt->style, NULL)) != 0) fatal_fr(r, "parse"); debug3_f("service=%s, style=%s", authctxt->service, authctxt->style); if (strlen(authctxt->style) == 0) { free(authctxt->style); authctxt->style = NULL; } return (0); } /* * Check that the key type appears in the supplied pattern list, ignoring * mismatches in the signature algorithm. (Signature algorithm checks are * performed in the unprivileged authentication code). * Returns 1 on success, 0 otherwise. */ static int key_base_type_match(const char *method, const struct sshkey *key, const char *list) { char *s, *l, *ol = xstrdup(list); int found = 0; l = ol; for ((s = strsep(&l, ",")); s && *s != '\0'; (s = strsep(&l, ","))) { if (sshkey_type_from_name(s) == key->type) { found = 1; break; } } if (!found) { error("%s key type %s is not in permitted list %s", method, sshkey_ssh_name(key), list); } free(ol); return found; } int mm_answer_authpassword(struct ssh *ssh, int sock, struct sshbuf *m) { static int call_count; char *passwd; int r, authenticated; size_t plen; if (!options.password_authentication) fatal_f("password authentication not enabled"); if ((r = sshbuf_get_cstring(m, &passwd, &plen)) != 0) fatal_fr(r, "parse"); /* Only authenticate if the context is valid */ authenticated = options.password_authentication && auth_password(ssh, passwd); freezero(passwd, plen); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, authenticated)) != 0) fatal_fr(r, "assemble"); #ifdef USE_PAM if ((r = sshbuf_put_u32(m, sshpam_get_maxtries_reached())) != 0) fatal_fr(r, "assemble PAM"); #endif debug3("%s: sending result %d", __func__, authenticated); debug3_f("sending result %d", authenticated); mm_request_send(sock, MONITOR_ANS_AUTHPASSWORD, m); call_count++; if (plen == 0 && call_count == 1) auth_method = "none"; else auth_method = "password"; /* Causes monitor loop to terminate if authenticated */ return (authenticated); } #ifdef BSD_AUTH int mm_answer_bsdauthquery(struct ssh *ssh, int sock, struct sshbuf *m) { char *name, *infotxt; u_int numprompts, *echo_on, success; char **prompts; int r; if (!options.kbd_interactive_authentication) fatal_f("kbd-int authentication not enabled"); success = bsdauth_query(authctxt, &name, &infotxt, &numprompts, &prompts, &echo_on) < 0 ? 0 : 1; sshbuf_reset(m); if ((r = sshbuf_put_u32(m, success)) != 0) fatal_fr(r, "assemble"); if (success) { if ((r = sshbuf_put_cstring(m, prompts[0])) != 0) fatal_fr(r, "assemble prompt"); } debug3_f("sending challenge success: %u", success); mm_request_send(sock, MONITOR_ANS_BSDAUTHQUERY, m); if (success) { free(name); free(infotxt); free(prompts); free(echo_on); } return (0); } int mm_answer_bsdauthrespond(struct ssh *ssh, int sock, struct sshbuf *m) { char *response; int r, authok; if (!options.kbd_interactive_authentication) fatal_f("kbd-int authentication not enabled"); if (authctxt->as == NULL) fatal_f("no bsd auth session"); if ((r = sshbuf_get_cstring(m, &response, NULL)) != 0) fatal_fr(r, "parse"); authok = options.kbd_interactive_authentication && auth_userresponse(authctxt->as, response, 0); authctxt->as = NULL; debug3_f("<%s> = <%d>", response, authok); free(response); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, authok)) != 0) fatal_fr(r, "assemble"); debug3_f("sending authenticated: %d", authok); mm_request_send(sock, MONITOR_ANS_BSDAUTHRESPOND, m); auth_method = "keyboard-interactive"; auth_submethod = "bsdauth"; return (authok != 0); } #endif #ifdef USE_PAM int mm_answer_pam_start(struct ssh *ssh, int sock, struct sshbuf *m) { if (!options.use_pam) fatal("UsePAM not set, but ended up in %s anyway", __func__); start_pam(ssh); monitor_permit(mon_dispatch, MONITOR_REQ_PAM_ACCOUNT, 1); if (options.kbd_interactive_authentication) monitor_permit(mon_dispatch, MONITOR_REQ_PAM_INIT_CTX, 1); return (0); } int mm_answer_pam_account(struct ssh *ssh, int sock, struct sshbuf *m) { u_int ret; int r; if (!options.use_pam) fatal("%s: PAM not enabled", __func__); ret = do_pam_account(); if ((r = sshbuf_put_u32(m, ret)) != 0 || (r = sshbuf_put_stringb(m, loginmsg)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); mm_request_send(sock, MONITOR_ANS_PAM_ACCOUNT, m); return (ret); } static void *sshpam_ctxt, *sshpam_authok; extern KbdintDevice sshpam_device; int mm_answer_pam_init_ctx(struct ssh *ssh, int sock, struct sshbuf *m) { u_int ok = 0; int r; debug3("%s", __func__); if (!options.kbd_interactive_authentication) fatal("%s: kbd-int authentication not enabled", __func__); if (sshpam_ctxt != NULL) fatal("%s: already called", __func__); sshpam_ctxt = (sshpam_device.init_ctx)(authctxt); sshpam_authok = NULL; sshbuf_reset(m); if (sshpam_ctxt != NULL) { monitor_permit(mon_dispatch, MONITOR_REQ_PAM_FREE_CTX, 1); monitor_permit(mon_dispatch, MONITOR_REQ_PAM_QUERY, 1); ok = 1; } if ((r = sshbuf_put_u32(m, ok)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); mm_request_send(sock, MONITOR_ANS_PAM_INIT_CTX, m); return (0); } int mm_answer_pam_query(struct ssh *ssh, int sock, struct sshbuf *m) { char *name = NULL, *info = NULL, **prompts = NULL; u_int i, num = 0, *echo_on = 0; int r, ret; debug3("%s", __func__); sshpam_authok = NULL; if (sshpam_ctxt == NULL) fatal("%s: no context", __func__); ret = (sshpam_device.query)(sshpam_ctxt, &name, &info, &num, &prompts, &echo_on); if (ret == 0 && num == 0) sshpam_authok = sshpam_ctxt; if (num > 1 || name == NULL || info == NULL) fatal("sshpam_device.query failed"); monitor_permit(mon_dispatch, MONITOR_REQ_PAM_RESPOND, 1); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, ret)) != 0 || (r = sshbuf_put_cstring(m, name)) != 0 || (r = sshbuf_put_cstring(m, info)) != 0 || (r = sshbuf_put_u32(m, sshpam_get_maxtries_reached())) != 0 || (r = sshbuf_put_u32(m, num)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); free(name); free(info); for (i = 0; i < num; ++i) { if ((r = sshbuf_put_cstring(m, prompts[i])) != 0 || (r = sshbuf_put_u32(m, echo_on[i])) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); free(prompts[i]); } free(prompts); free(echo_on); auth_method = "keyboard-interactive"; auth_submethod = "pam"; mm_request_send(sock, MONITOR_ANS_PAM_QUERY, m); return (0); } int mm_answer_pam_respond(struct ssh *ssh, int sock, struct sshbuf *m) { char **resp; u_int i, num; int r, ret; debug3("%s", __func__); if (sshpam_ctxt == NULL) fatal("%s: no context", __func__); sshpam_authok = NULL; if ((r = sshbuf_get_u32(m, &num)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (num > PAM_MAX_NUM_MSG) { fatal_f("Too many PAM messages, got %u, expected <= %u", num, (unsigned)PAM_MAX_NUM_MSG); } if (num > 0) { resp = xcalloc(num, sizeof(char *)); for (i = 0; i < num; ++i) { if ((r = sshbuf_get_cstring(m, &(resp[i]), NULL)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); } ret = (sshpam_device.respond)(sshpam_ctxt, num, resp); for (i = 0; i < num; ++i) free(resp[i]); free(resp); } else { ret = (sshpam_device.respond)(sshpam_ctxt, num, NULL); } sshbuf_reset(m); if ((r = sshbuf_put_u32(m, ret)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); mm_request_send(sock, MONITOR_ANS_PAM_RESPOND, m); auth_method = "keyboard-interactive"; auth_submethod = "pam"; if (ret == 0) sshpam_authok = sshpam_ctxt; return (0); } int mm_answer_pam_free_ctx(struct ssh *ssh, int sock, struct sshbuf *m) { int r = sshpam_authok != NULL && sshpam_authok == sshpam_ctxt; debug3("%s", __func__); if (sshpam_ctxt == NULL) fatal("%s: no context", __func__); (sshpam_device.free_ctx)(sshpam_ctxt); sshpam_ctxt = sshpam_authok = NULL; sshbuf_reset(m); mm_request_send(sock, MONITOR_ANS_PAM_FREE_CTX, m); /* Allow another attempt */ monitor_permit(mon_dispatch, MONITOR_REQ_PAM_INIT_CTX, 1); auth_method = "keyboard-interactive"; auth_submethod = "pam"; return r; } #endif int mm_answer_keyallowed(struct ssh *ssh, int sock, struct sshbuf *m) { struct sshkey *key = NULL; char *cuser, *chost; u_int pubkey_auth_attempt; u_int type = 0; int r, allowed = 0; struct sshauthopt *opts = NULL; debug3_f("entering"); if ((r = sshbuf_get_u32(m, &type)) != 0 || (r = sshbuf_get_cstring(m, &cuser, NULL)) != 0 || (r = sshbuf_get_cstring(m, &chost, NULL)) != 0 || (r = sshkey_froms(m, &key)) != 0 || (r = sshbuf_get_u32(m, &pubkey_auth_attempt)) != 0) fatal_fr(r, "parse"); if (key != NULL && authctxt->valid) { switch (type) { case MM_USERKEY: auth_method = "publickey"; if (!options.pubkey_authentication) break; if (auth2_key_already_used(authctxt, key)) break; if (!key_base_type_match(auth_method, key, options.pubkey_accepted_algos)) break; allowed = user_key_allowed(ssh, authctxt->pw, key, pubkey_auth_attempt, &opts); break; case MM_HOSTKEY: auth_method = "hostbased"; if (!options.hostbased_authentication) break; if (auth2_key_already_used(authctxt, key)) break; if (!key_base_type_match(auth_method, key, options.hostbased_accepted_algos)) break; allowed = hostbased_key_allowed(ssh, authctxt->pw, cuser, chost, key); auth2_record_info(authctxt, "client user \"%.100s\", client host \"%.100s\"", cuser, chost); break; default: fatal_f("unknown key type %u", type); break; } } debug3_f("%s authentication%s: %s key is %s", auth_method, pubkey_auth_attempt ? "" : " test", (key == NULL || !authctxt->valid) ? "invalid" : sshkey_type(key), allowed ? "allowed" : "not allowed"); auth2_record_key(authctxt, 0, key); /* clear temporarily storage (used by verify) */ monitor_reset_key_state(); if (allowed) { /* Save temporarily for comparison in verify */ if ((r = sshkey_to_blob(key, &key_blob, &key_bloblen)) != 0) fatal_fr(r, "sshkey_to_blob"); key_blobtype = type; key_opts = opts; hostbased_cuser = cuser; hostbased_chost = chost; } else { /* Log failed attempt */ auth_log(ssh, 0, 0, auth_method, NULL); free(cuser); free(chost); } sshkey_free(key); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, allowed)) != 0) fatal_fr(r, "assemble"); if (opts != NULL && (r = sshauthopt_serialise(opts, m, 1)) != 0) fatal_fr(r, "sshauthopt_serialise"); mm_request_send(sock, MONITOR_ANS_KEYALLOWED, m); if (!allowed) sshauthopt_free(opts); return (0); } static int monitor_valid_userblob(struct ssh *ssh, const u_char *data, u_int datalen) { struct sshbuf *b; struct sshkey *hostkey = NULL; const u_char *p; char *userstyle, *cp; size_t len; u_char type; int hostbound = 0, r, fail = 0; if ((b = sshbuf_from(data, datalen)) == NULL) fatal_f("sshbuf_from"); if (ssh->compat & SSH_OLD_SESSIONID) { p = sshbuf_ptr(b); len = sshbuf_len(b); if ((session_id2 == NULL) || (len < session_id2_len) || (timingsafe_bcmp(p, session_id2, session_id2_len) != 0)) fail++; if ((r = sshbuf_consume(b, session_id2_len)) != 0) fatal_fr(r, "consume"); } else { if ((r = sshbuf_get_string_direct(b, &p, &len)) != 0) fatal_fr(r, "parse sessionid"); if ((session_id2 == NULL) || (len != session_id2_len) || (timingsafe_bcmp(p, session_id2, session_id2_len) != 0)) fail++; } if ((r = sshbuf_get_u8(b, &type)) != 0) fatal_fr(r, "parse type"); if (type != SSH2_MSG_USERAUTH_REQUEST) fail++; if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0) fatal_fr(r, "parse userstyle"); xasprintf(&userstyle, "%s%s%s", authctxt->user, authctxt->style ? ":" : "", authctxt->style ? authctxt->style : ""); if (strcmp(userstyle, cp) != 0) { logit("wrong user name passed to monitor: " "expected %s != %.100s", userstyle, cp); fail++; } free(userstyle); free(cp); if ((r = sshbuf_skip_string(b)) != 0 || /* service */ (r = sshbuf_get_cstring(b, &cp, NULL)) != 0) fatal_fr(r, "parse method"); if (strcmp("publickey", cp) != 0) { if (strcmp("publickey-hostbound-v00@openssh.com", cp) == 0) hostbound = 1; else fail++; } free(cp); if ((r = sshbuf_get_u8(b, &type)) != 0) fatal_fr(r, "parse pktype"); if (type == 0) fail++; if ((r = sshbuf_skip_string(b)) != 0 || /* pkalg */ (r = sshbuf_skip_string(b)) != 0 || /* pkblob */ (hostbound && (r = sshkey_froms(b, &hostkey)) != 0)) fatal_fr(r, "parse pk"); if (sshbuf_len(b) != 0) fail++; sshbuf_free(b); if (hostkey != NULL) { /* * Ensure this is actually one of our hostkeys; unfortunately * can't check ssh->kex->initial_hostkey directly at this point * as packet state has not yet been exported to monitor. */ if (get_hostkey_index(hostkey, 1, ssh) == -1) fatal_f("hostbound hostkey does not match"); sshkey_free(hostkey); } return (fail == 0); } static int monitor_valid_hostbasedblob(const u_char *data, u_int datalen, const char *cuser, const char *chost) { struct sshbuf *b; const u_char *p; char *cp, *userstyle; size_t len; int r, fail = 0; u_char type; if ((b = sshbuf_from(data, datalen)) == NULL) fatal_f("sshbuf_new"); if ((r = sshbuf_get_string_direct(b, &p, &len)) != 0) fatal_fr(r, "parse sessionid"); if ((session_id2 == NULL) || (len != session_id2_len) || (timingsafe_bcmp(p, session_id2, session_id2_len) != 0)) fail++; if ((r = sshbuf_get_u8(b, &type)) != 0) fatal_fr(r, "parse type"); if (type != SSH2_MSG_USERAUTH_REQUEST) fail++; if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0) fatal_fr(r, "parse userstyle"); xasprintf(&userstyle, "%s%s%s", authctxt->user, authctxt->style ? ":" : "", authctxt->style ? authctxt->style : ""); if (strcmp(userstyle, cp) != 0) { logit("wrong user name passed to monitor: " "expected %s != %.100s", userstyle, cp); fail++; } free(userstyle); free(cp); if ((r = sshbuf_skip_string(b)) != 0 || /* service */ (r = sshbuf_get_cstring(b, &cp, NULL)) != 0) fatal_fr(r, "parse method"); if (strcmp(cp, "hostbased") != 0) fail++; free(cp); if ((r = sshbuf_skip_string(b)) != 0 || /* pkalg */ (r = sshbuf_skip_string(b)) != 0) /* pkblob */ fatal_fr(r, "parse pk"); /* verify client host, strip trailing dot if necessary */ if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0) fatal_fr(r, "parse host"); if (((len = strlen(cp)) > 0) && cp[len - 1] == '.') cp[len - 1] = '\0'; if (strcmp(cp, chost) != 0) fail++; free(cp); /* verify client user */ if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0) fatal_fr(r, "parse ruser"); if (strcmp(cp, cuser) != 0) fail++; free(cp); if (sshbuf_len(b) != 0) fail++; sshbuf_free(b); return (fail == 0); } int mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m) { struct sshkey *key; const u_char *signature, *data, *blob; char *sigalg = NULL, *fp = NULL; size_t signaturelen, datalen, bloblen; int r, ret, req_presence = 0, req_verify = 0, valid_data = 0; int encoded_ret; struct sshkey_sig_details *sig_details = NULL; if ((r = sshbuf_get_string_direct(m, &blob, &bloblen)) != 0 || (r = sshbuf_get_string_direct(m, &signature, &signaturelen)) != 0 || (r = sshbuf_get_string_direct(m, &data, &datalen)) != 0 || (r = sshbuf_get_cstring(m, &sigalg, NULL)) != 0) fatal_fr(r, "parse"); if (hostbased_cuser == NULL || hostbased_chost == NULL || !monitor_allowed_key(blob, bloblen)) fatal_f("bad key, not previously allowed"); /* Empty signature algorithm means NULL. */ if (*sigalg == '\0') { free(sigalg); sigalg = NULL; } /* XXX use sshkey_froms here; need to change key_blob, etc. */ if ((r = sshkey_from_blob(blob, bloblen, &key)) != 0) fatal_fr(r, "parse key"); switch (key_blobtype) { case MM_USERKEY: valid_data = monitor_valid_userblob(ssh, data, datalen); auth_method = "publickey"; break; case MM_HOSTKEY: valid_data = monitor_valid_hostbasedblob(data, datalen, hostbased_cuser, hostbased_chost); auth_method = "hostbased"; break; default: valid_data = 0; break; } if (!valid_data) fatal_f("bad %s signature data blob", key_blobtype == MM_USERKEY ? "userkey" : (key_blobtype == MM_HOSTKEY ? "hostkey" : "unknown")); if ((fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) fatal_f("sshkey_fingerprint failed"); ret = sshkey_verify(key, signature, signaturelen, data, datalen, sigalg, ssh->compat, &sig_details); debug3_f("%s %s signature using %s %s%s%s", auth_method, sshkey_type(key), sigalg == NULL ? "default" : sigalg, (ret == 0) ? "verified" : "unverified", (ret != 0) ? ": " : "", (ret != 0) ? ssh_err(ret) : ""); if (ret == 0 && key_blobtype == MM_USERKEY && sig_details != NULL) { req_presence = (options.pubkey_auth_options & PUBKEYAUTH_TOUCH_REQUIRED) || !key_opts->no_require_user_presence; if (req_presence && (sig_details->sk_flags & SSH_SK_USER_PRESENCE_REQD) == 0) { error("public key %s %s signature for %s%s from %.128s " "port %d rejected: user presence " "(authenticator touch) requirement not met ", sshkey_type(key), fp, authctxt->valid ? "" : "invalid user ", authctxt->user, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); ret = SSH_ERR_SIGNATURE_INVALID; } req_verify = (options.pubkey_auth_options & PUBKEYAUTH_VERIFY_REQUIRED) || key_opts->require_verify; if (req_verify && (sig_details->sk_flags & SSH_SK_USER_VERIFICATION_REQD) == 0) { error("public key %s %s signature for %s%s from %.128s " "port %d rejected: user verification requirement " "not met ", sshkey_type(key), fp, authctxt->valid ? "" : "invalid user ", authctxt->user, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); ret = SSH_ERR_SIGNATURE_INVALID; } } auth2_record_key(authctxt, ret == 0, key); if (key_blobtype == MM_USERKEY && ret == 0) auth_activate_options(ssh, key_opts); monitor_reset_key_state(); sshbuf_reset(m); /* encode ret != 0 as positive integer, since we're sending u32 */ encoded_ret = (ret != 0); if ((r = sshbuf_put_u32(m, encoded_ret)) != 0 || (r = sshbuf_put_u8(m, sig_details != NULL)) != 0) fatal_fr(r, "assemble"); if (sig_details != NULL) { if ((r = sshbuf_put_u32(m, sig_details->sk_counter)) != 0 || (r = sshbuf_put_u8(m, sig_details->sk_flags)) != 0) fatal_fr(r, "assemble sk"); } sshkey_sig_details_free(sig_details); mm_request_send(sock, MONITOR_ANS_KEYVERIFY, m); free(sigalg); free(fp); sshkey_free(key); return ret == 0; } static void mm_record_login(struct ssh *ssh, Session *s, struct passwd *pw) { socklen_t fromlen; struct sockaddr_storage from; /* * Get IP address of client. If the connection is not a socket, let * the address be 0.0.0.0. */ memset(&from, 0, sizeof(from)); fromlen = sizeof(from); if (ssh_packet_connection_is_on_socket(ssh)) { if (getpeername(ssh_packet_get_connection_in(ssh), (struct sockaddr *)&from, &fromlen) == -1) { debug("getpeername: %.100s", strerror(errno)); cleanup_exit(255); } } /* Record that there was a login on that tty from the remote host. */ record_login(s->pid, s->tty, pw->pw_name, pw->pw_uid, session_get_remote_name_or_ip(ssh, utmp_len, options.use_dns), (struct sockaddr *)&from, fromlen); } static void mm_session_close(Session *s) { debug3_f("session %d pid %ld", s->self, (long)s->pid); if (s->ttyfd != -1) { debug3_f("tty %s ptyfd %d", s->tty, s->ptyfd); session_pty_cleanup2(s); } session_unused(s->self); } int mm_answer_pty(struct ssh *ssh, int sock, struct sshbuf *m) { extern struct monitor *pmonitor; Session *s; int r, res, fd0; debug3_f("entering"); sshbuf_reset(m); s = session_new(); if (s == NULL) goto error; s->authctxt = authctxt; s->pw = authctxt->pw; s->pid = pmonitor->m_pid; res = pty_allocate(&s->ptyfd, &s->ttyfd, s->tty, sizeof(s->tty)); if (res == 0) goto error; pty_setowner(authctxt->pw, s->tty); if ((r = sshbuf_put_u32(m, 1)) != 0 || (r = sshbuf_put_cstring(m, s->tty)) != 0) fatal_fr(r, "assemble"); /* We need to trick ttyslot */ if (dup2(s->ttyfd, 0) == -1) fatal_f("dup2"); mm_record_login(ssh, s, authctxt->pw); /* Now we can close the file descriptor again */ close(0); /* send messages generated by record_login */ if ((r = sshbuf_put_stringb(m, loginmsg)) != 0) fatal_fr(r, "assemble loginmsg"); sshbuf_reset(loginmsg); mm_request_send(sock, MONITOR_ANS_PTY, m); if (mm_send_fd(sock, s->ptyfd) == -1 || mm_send_fd(sock, s->ttyfd) == -1) fatal_f("send fds failed"); /* make sure nothing uses fd 0 */ if ((fd0 = open(_PATH_DEVNULL, O_RDONLY)) == -1) fatal_f("open(/dev/null): %s", strerror(errno)); if (fd0 != 0) error_f("fd0 %d != 0", fd0); /* slave side of pty is not needed */ close(s->ttyfd); s->ttyfd = s->ptyfd; /* no need to dup() because nobody closes ptyfd */ s->ptymaster = s->ptyfd; debug3_f("tty %s ptyfd %d", s->tty, s->ttyfd); return (0); error: if (s != NULL) mm_session_close(s); if ((r = sshbuf_put_u32(m, 0)) != 0) fatal_fr(r, "assemble 0"); mm_request_send(sock, MONITOR_ANS_PTY, m); return (0); } int mm_answer_pty_cleanup(struct ssh *ssh, int sock, struct sshbuf *m) { Session *s; char *tty; int r; debug3_f("entering"); if ((r = sshbuf_get_cstring(m, &tty, NULL)) != 0) fatal_fr(r, "parse tty"); if ((s = session_by_tty(tty)) != NULL) mm_session_close(s); sshbuf_reset(m); free(tty); return (0); } int mm_answer_term(struct ssh *ssh, int sock, struct sshbuf *req) { extern struct monitor *pmonitor; int res, status; debug3_f("tearing down sessions"); /* The child is terminating */ session_destroy_all(ssh, &mm_session_close); #ifdef USE_PAM if (options.use_pam) sshpam_cleanup(); #endif while (waitpid(pmonitor->m_pid, &status, 0) == -1) if (errno != EINTR) exit(1); res = WIFEXITED(status) ? WEXITSTATUS(status) : 1; /* Terminate process */ exit(res); } #ifdef SSH_AUDIT_EVENTS /* Report that an audit event occurred */ int mm_answer_audit_event(struct ssh *ssh, int socket, struct sshbuf *m) { u_int n; ssh_audit_event_t event; int r; debug3("%s entering", __func__); if ((r = sshbuf_get_u32(m, &n)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); event = (ssh_audit_event_t)n; switch (event) { case SSH_AUTH_FAIL_PUBKEY: case SSH_AUTH_FAIL_HOSTBASED: case SSH_AUTH_FAIL_GSSAPI: case SSH_LOGIN_EXCEED_MAXTRIES: case SSH_LOGIN_ROOT_DENIED: case SSH_CONNECTION_CLOSE: case SSH_INVALID_USER: audit_event(ssh, event); break; default: fatal("Audit event type %d not permitted", event); } return (0); } int mm_answer_audit_command(struct ssh *ssh, int socket, struct sshbuf *m) { char *cmd; int r; debug3("%s entering", __func__); if ((r = sshbuf_get_cstring(m, &cmd, NULL)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); /* sanity check command, if so how? */ audit_run_command(cmd); free(cmd); return (0); } #endif /* SSH_AUDIT_EVENTS */ void monitor_clear_keystate(struct ssh *ssh, struct monitor *pmonitor) { ssh_clear_newkeys(ssh, MODE_IN); ssh_clear_newkeys(ssh, MODE_OUT); sshbuf_free(child_state); child_state = NULL; } void monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor) { struct kex *kex; int r; debug3_f("packet_set_state"); if ((r = ssh_packet_set_state(ssh, child_state)) != 0) fatal_fr(r, "packet_set_state"); sshbuf_free(child_state); child_state = NULL; if ((kex = ssh->kex) == NULL) fatal_f("internal error: ssh->kex == NULL"); if (session_id2_len != sshbuf_len(ssh->kex->session_id)) { fatal_f("incorrect session id length %zu (expected %u)", sshbuf_len(ssh->kex->session_id), session_id2_len); } if (memcmp(sshbuf_ptr(ssh->kex->session_id), session_id2, session_id2_len) != 0) fatal_f("session ID mismatch"); /* XXX set callbacks */ #ifdef WITH_OPENSSL kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_server; kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_server; kex->kex[KEX_DH_GRP14_SHA256] = kex_gen_server; kex->kex[KEX_DH_GRP16_SHA512] = kex_gen_server; kex->kex[KEX_DH_GRP18_SHA512] = kex_gen_server; kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; # ifdef OPENSSL_HAS_ECC kex->kex[KEX_ECDH_SHA2] = kex_gen_server; # endif #endif /* WITH_OPENSSL */ kex->kex[KEX_C25519_SHA256] = kex_gen_server; kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server; kex->load_host_public_key=&get_hostkey_public_by_type; kex->load_host_private_key=&get_hostkey_private_by_type; kex->host_key_index=&get_hostkey_index; kex->sign = sshd_hostkey_sign; } /* This function requires careful sanity checking */ void mm_get_keystate(struct ssh *ssh, struct monitor *pmonitor) { debug3_f("Waiting for new keys"); if ((child_state = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); mm_request_receive_expect(pmonitor->m_sendfd, MONITOR_REQ_KEYEXPORT, child_state); debug3_f("GOT new keys"); } /* XXX */ #define FD_CLOSEONEXEC(x) do { \ if (fcntl(x, F_SETFD, FD_CLOEXEC) == -1) \ fatal("fcntl(%d, F_SETFD)", x); \ } while (0) static void monitor_openfds(struct monitor *mon, int do_logfds) { int pair[2]; #ifdef SO_ZEROIZE int on = 1; #endif if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) fatal_f("socketpair: %s", strerror(errno)); #ifdef SO_ZEROIZE if (setsockopt(pair[0], SOL_SOCKET, SO_ZEROIZE, &on, sizeof(on)) == -1) error("setsockopt SO_ZEROIZE(0): %.100s", strerror(errno)); if (setsockopt(pair[1], SOL_SOCKET, SO_ZEROIZE, &on, sizeof(on)) == -1) error("setsockopt SO_ZEROIZE(1): %.100s", strerror(errno)); #endif FD_CLOSEONEXEC(pair[0]); FD_CLOSEONEXEC(pair[1]); mon->m_recvfd = pair[0]; mon->m_sendfd = pair[1]; if (do_logfds) { if (pipe(pair) == -1) fatal_f("pipe: %s", strerror(errno)); FD_CLOSEONEXEC(pair[0]); FD_CLOSEONEXEC(pair[1]); mon->m_log_recvfd = pair[0]; mon->m_log_sendfd = pair[1]; } else mon->m_log_recvfd = mon->m_log_sendfd = -1; } struct monitor * monitor_init(void) { struct monitor *mon; mon = xcalloc(1, sizeof(*mon)); monitor_openfds(mon, 1); return mon; } void monitor_reinit(struct monitor *mon) { monitor_openfds(mon, 0); } #ifdef GSSAPI int mm_answer_gss_setup_ctx(struct ssh *ssh, int sock, struct sshbuf *m) { gss_OID_desc goid; OM_uint32 major; size_t len; u_char *p; int r; if (!options.gss_authentication) fatal_f("GSSAPI authentication not enabled"); if ((r = sshbuf_get_string(m, &p, &len)) != 0) fatal_fr(r, "parse"); goid.elements = p; goid.length = len; major = ssh_gssapi_server_ctx(&gsscontext, &goid); free(goid.elements); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, major)) != 0) fatal_fr(r, "assemble"); mm_request_send(sock, MONITOR_ANS_GSSSETUP, m); /* Now we have a context, enable the step */ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 1); return (0); } int mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m) { gss_buffer_desc in; gss_buffer_desc out = GSS_C_EMPTY_BUFFER; OM_uint32 major, minor; OM_uint32 flags = 0; /* GSI needs this */ int r; if (!options.gss_authentication) fatal_f("GSSAPI authentication not enabled"); if ((r = ssh_gssapi_get_buffer_desc(m, &in)) != 0) fatal_fr(r, "ssh_gssapi_get_buffer_desc"); major = ssh_gssapi_accept_ctx(gsscontext, &in, &out, &flags); free(in.value); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, major)) != 0 || (r = sshbuf_put_string(m, out.value, out.length)) != 0 || (r = sshbuf_put_u32(m, flags)) != 0) fatal_fr(r, "assemble"); mm_request_send(sock, MONITOR_ANS_GSSSTEP, m); gss_release_buffer(&minor, &out); if (major == GSS_S_COMPLETE) { monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0); monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1); monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1); } return (0); } int mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m) { gss_buffer_desc gssbuf, mic; OM_uint32 ret; int r; if (!options.gss_authentication) fatal_f("GSSAPI authentication not enabled"); if ((r = ssh_gssapi_get_buffer_desc(m, &gssbuf)) != 0 || (r = ssh_gssapi_get_buffer_desc(m, &mic)) != 0) fatal_fr(r, "ssh_gssapi_get_buffer_desc"); ret = ssh_gssapi_checkmic(gsscontext, &gssbuf, &mic); free(gssbuf.value); free(mic.value); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, ret)) != 0) fatal_fr(r, "assemble"); mm_request_send(sock, MONITOR_ANS_GSSCHECKMIC, m); if (!GSS_ERROR(ret)) monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1); return (0); } int mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m) { int r, authenticated; const char *displayname; if (!options.gss_authentication) fatal_f("GSSAPI authentication not enabled"); authenticated = authctxt->valid && ssh_gssapi_userok(authctxt->user); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, authenticated)) != 0) fatal_fr(r, "assemble"); debug3_f("sending result %d", authenticated); mm_request_send(sock, MONITOR_ANS_GSSUSEROK, m); auth_method = "gssapi-with-mic"; if ((displayname = ssh_gssapi_displayname()) != NULL) auth2_record_info(authctxt, "%s", displayname); /* Monitor loop will terminate if authenticated */ return (authenticated); } #endif /* GSSAPI */ diff --git a/crypto/openssh/packet.c b/crypto/openssh/packet.c index cc114c837e31..9dea2cfc5188 100644 --- a/crypto/openssh/packet.c +++ b/crypto/openssh/packet.c @@ -1,2871 +1,2869 @@ /* $OpenBSD: packet.c,v 1.318 2025/02/18 08:02:12 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * This file contains code implementing the packet protocol and communication * with the other side. This same code is used both on client and server side. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * * SSH2 packet format added by Markus Friedl. * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include "openbsd-compat/sys-queue.h" #include #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_POLL_H #include #endif #include #include /* * Explicitly include OpenSSL before zlib as some versions of OpenSSL have * "free_func" in their headers, which zlib typedefs. */ #ifdef WITH_OPENSSL # include # include # ifdef OPENSSL_HAS_ECC # include # endif #endif #ifdef WITH_ZLIB #include #endif #include "xmalloc.h" #include "compat.h" #include "ssh2.h" #include "cipher.h" #include "sshkey.h" #include "kex.h" #include "digest.h" #include "mac.h" #include "log.h" #include "canohost.h" #include "misc.h" #include "channels.h" #include "ssh.h" #include "packet.h" #include "ssherr.h" #include "sshbuf.h" -#include "blacklist_client.h" #ifdef PACKET_DEBUG #define DBG(x) x #else #define DBG(x) #endif #define PACKET_MAX_SIZE (256 * 1024) struct packet_state { u_int32_t seqnr; u_int32_t packets; u_int64_t blocks; u_int64_t bytes; }; struct packet { TAILQ_ENTRY(packet) next; u_char type; struct sshbuf *payload; }; struct session_state { /* * This variable contains the file descriptors used for * communicating with the other side. connection_in is used for * reading; connection_out for writing. These can be the same * descriptor, in which case it is assumed to be a socket. */ int connection_in; int connection_out; /* Protocol flags for the remote side. */ u_int remote_protocol_flags; /* Encryption context for receiving data. Only used for decryption. */ struct sshcipher_ctx *receive_context; /* Encryption context for sending data. Only used for encryption. */ struct sshcipher_ctx *send_context; /* Buffer for raw input data from the socket. */ struct sshbuf *input; /* Buffer for raw output data going to the socket. */ struct sshbuf *output; /* Buffer for the partial outgoing packet being constructed. */ struct sshbuf *outgoing_packet; /* Buffer for the incoming packet currently being processed. */ struct sshbuf *incoming_packet; /* Scratch buffer for packet compression/decompression. */ struct sshbuf *compression_buffer; #ifdef WITH_ZLIB /* Incoming/outgoing compression dictionaries */ z_stream compression_in_stream; z_stream compression_out_stream; #endif int compression_in_started; int compression_out_started; int compression_in_failures; int compression_out_failures; /* default maximum packet size */ u_int max_packet_size; /* Flag indicating whether this module has been initialized. */ int initialized; /* Set to true if the connection is interactive. */ int interactive_mode; /* Set to true if we are the server side. */ int server_side; /* Set to true if we are authenticated. */ int after_authentication; int keep_alive_timeouts; /* The maximum time that we will wait to send or receive a packet */ int packet_timeout_ms; /* Session key information for Encryption and MAC */ struct newkeys *newkeys[MODE_MAX]; struct packet_state p_read, p_send; /* Volume-based rekeying */ u_int64_t max_blocks_in, max_blocks_out, rekey_limit; /* Time-based rekeying */ u_int32_t rekey_interval; /* how often in seconds */ time_t rekey_time; /* time of last rekeying */ /* roundup current message to extra_pad bytes */ u_char extra_pad; /* XXX discard incoming data after MAC error */ u_int packet_discard; size_t packet_discard_mac_already; struct sshmac *packet_discard_mac; /* Used in packet_read_poll2() */ u_int packlen; /* Used in packet_send2 */ int rekeying; /* Used in ssh_packet_send_mux() */ int mux; /* Used in packet_set_interactive */ int set_interactive_called; /* Used in packet_set_maxsize */ int set_maxsize_called; /* One-off warning about weak ciphers */ int cipher_warning_done; /* Hook for fuzzing inbound packets */ ssh_packet_hook_fn *hook_in; void *hook_in_ctx; TAILQ_HEAD(, packet) outgoing; }; struct ssh * ssh_alloc_session_state(void) { struct ssh *ssh = NULL; struct session_state *state = NULL; if ((ssh = calloc(1, sizeof(*ssh))) == NULL || (state = calloc(1, sizeof(*state))) == NULL || (ssh->kex = kex_new()) == NULL || (state->input = sshbuf_new()) == NULL || (state->output = sshbuf_new()) == NULL || (state->outgoing_packet = sshbuf_new()) == NULL || (state->incoming_packet = sshbuf_new()) == NULL) goto fail; TAILQ_INIT(&state->outgoing); TAILQ_INIT(&ssh->private_keys); TAILQ_INIT(&ssh->public_keys); state->connection_in = -1; state->connection_out = -1; state->max_packet_size = 32768; state->packet_timeout_ms = -1; state->p_send.packets = state->p_read.packets = 0; state->initialized = 1; /* * ssh_packet_send2() needs to queue packets until * we've done the initial key exchange. */ state->rekeying = 1; ssh->state = state; return ssh; fail: if (ssh) { kex_free(ssh->kex); free(ssh); } if (state) { sshbuf_free(state->input); sshbuf_free(state->output); sshbuf_free(state->incoming_packet); sshbuf_free(state->outgoing_packet); free(state); } return NULL; } void ssh_packet_set_input_hook(struct ssh *ssh, ssh_packet_hook_fn *hook, void *ctx) { ssh->state->hook_in = hook; ssh->state->hook_in_ctx = ctx; } /* Returns nonzero if rekeying is in progress */ int ssh_packet_is_rekeying(struct ssh *ssh) { return ssh->state->rekeying || (ssh->kex != NULL && ssh->kex->done == 0); } /* * Sets the descriptors used for communication. */ struct ssh * ssh_packet_set_connection(struct ssh *ssh, int fd_in, int fd_out) { struct session_state *state; const struct sshcipher *none = cipher_by_name("none"); int r; if (none == NULL) { error_f("cannot load cipher 'none'"); return NULL; } if (ssh == NULL) ssh = ssh_alloc_session_state(); if (ssh == NULL) { error_f("could not allocate state"); return NULL; } state = ssh->state; state->connection_in = fd_in; state->connection_out = fd_out; if ((r = cipher_init(&state->send_context, none, (const u_char *)"", 0, NULL, 0, CIPHER_ENCRYPT)) != 0 || (r = cipher_init(&state->receive_context, none, (const u_char *)"", 0, NULL, 0, CIPHER_DECRYPT)) != 0) { error_fr(r, "cipher_init failed"); free(ssh); /* XXX need ssh_free_session_state? */ return NULL; } state->newkeys[MODE_IN] = state->newkeys[MODE_OUT] = NULL; /* * Cache the IP address of the remote connection for use in error * messages that might be generated after the connection has closed. */ (void)ssh_remote_ipaddr(ssh); return ssh; } void ssh_packet_set_timeout(struct ssh *ssh, int timeout, int count) { struct session_state *state = ssh->state; if (timeout <= 0 || count <= 0) { state->packet_timeout_ms = -1; return; } if ((INT_MAX / 1000) / count < timeout) state->packet_timeout_ms = INT_MAX; else state->packet_timeout_ms = timeout * count * 1000; } void ssh_packet_set_mux(struct ssh *ssh) { ssh->state->mux = 1; ssh->state->rekeying = 0; kex_free(ssh->kex); ssh->kex = NULL; } int ssh_packet_get_mux(struct ssh *ssh) { return ssh->state->mux; } int ssh_packet_set_log_preamble(struct ssh *ssh, const char *fmt, ...) { va_list args; int r; free(ssh->log_preamble); if (fmt == NULL) ssh->log_preamble = NULL; else { va_start(args, fmt); r = vasprintf(&ssh->log_preamble, fmt, args); va_end(args); if (r < 0 || ssh->log_preamble == NULL) return SSH_ERR_ALLOC_FAIL; } return 0; } int ssh_packet_stop_discard(struct ssh *ssh) { struct session_state *state = ssh->state; int r; if (state->packet_discard_mac) { char buf[1024]; size_t dlen = PACKET_MAX_SIZE; if (dlen > state->packet_discard_mac_already) dlen -= state->packet_discard_mac_already; memset(buf, 'a', sizeof(buf)); while (sshbuf_len(state->incoming_packet) < dlen) if ((r = sshbuf_put(state->incoming_packet, buf, sizeof(buf))) != 0) return r; (void) mac_compute(state->packet_discard_mac, state->p_read.seqnr, sshbuf_ptr(state->incoming_packet), dlen, NULL, 0); } logit("Finished discarding for %.200s port %d", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); return SSH_ERR_MAC_INVALID; } static int ssh_packet_start_discard(struct ssh *ssh, struct sshenc *enc, struct sshmac *mac, size_t mac_already, u_int discard) { struct session_state *state = ssh->state; int r; if (enc == NULL || !cipher_is_cbc(enc->cipher) || (mac && mac->etm)) { if ((r = sshpkt_disconnect(ssh, "Packet corrupt")) != 0) return r; return SSH_ERR_MAC_INVALID; } /* * Record number of bytes over which the mac has already * been computed in order to minimize timing attacks. */ if (mac && mac->enabled) { state->packet_discard_mac = mac; state->packet_discard_mac_already = mac_already; } if (sshbuf_len(state->input) >= discard) return ssh_packet_stop_discard(ssh); state->packet_discard = discard - sshbuf_len(state->input); return 0; } /* Returns 1 if remote host is connected via socket, 0 if not. */ int ssh_packet_connection_is_on_socket(struct ssh *ssh) { struct session_state *state; struct sockaddr_storage from, to; socklen_t fromlen, tolen; if (ssh == NULL || ssh->state == NULL) return 0; state = ssh->state; if (state->connection_in == -1 || state->connection_out == -1) return 0; /* filedescriptors in and out are the same, so it's a socket */ if (state->connection_in == state->connection_out) return 1; fromlen = sizeof(from); memset(&from, 0, sizeof(from)); if (getpeername(state->connection_in, (struct sockaddr *)&from, &fromlen) == -1) return 0; tolen = sizeof(to); memset(&to, 0, sizeof(to)); if (getpeername(state->connection_out, (struct sockaddr *)&to, &tolen) == -1) return 0; if (fromlen != tolen || memcmp(&from, &to, fromlen) != 0) return 0; if (from.ss_family != AF_INET && from.ss_family != AF_INET6) return 0; return 1; } void ssh_packet_get_bytes(struct ssh *ssh, u_int64_t *ibytes, u_int64_t *obytes) { if (ibytes) *ibytes = ssh->state->p_read.bytes; if (obytes) *obytes = ssh->state->p_send.bytes; } int ssh_packet_connection_af(struct ssh *ssh) { return get_sock_af(ssh->state->connection_out); } /* Sets the connection into non-blocking mode. */ void ssh_packet_set_nonblocking(struct ssh *ssh) { /* Set the socket into non-blocking mode. */ set_nonblock(ssh->state->connection_in); if (ssh->state->connection_out != ssh->state->connection_in) set_nonblock(ssh->state->connection_out); } /* Returns the socket used for reading. */ int ssh_packet_get_connection_in(struct ssh *ssh) { return ssh->state->connection_in; } /* Returns the descriptor used for writing. */ int ssh_packet_get_connection_out(struct ssh *ssh) { return ssh->state->connection_out; } /* * Returns the IP-address of the remote host as a string. The returned * string must not be freed. */ const char * ssh_remote_ipaddr(struct ssh *ssh) { int sock; /* Check whether we have cached the ipaddr. */ if (ssh->remote_ipaddr == NULL) { if (ssh_packet_connection_is_on_socket(ssh)) { sock = ssh->state->connection_in; ssh->remote_ipaddr = get_peer_ipaddr(sock); ssh->remote_port = get_peer_port(sock); ssh->local_ipaddr = get_local_ipaddr(sock); ssh->local_port = get_local_port(sock); } else { ssh->remote_ipaddr = xstrdup("UNKNOWN"); ssh->remote_port = 65535; ssh->local_ipaddr = xstrdup("UNKNOWN"); ssh->local_port = 65535; } } return ssh->remote_ipaddr; } /* * Returns the remote DNS hostname as a string. The returned string must not * be freed. NB. this will usually trigger a DNS query. Return value is on * heap and no caching is performed. * This function does additional checks on the hostname to mitigate some * attacks based on conflation of hostnames and addresses and will * fall back to returning an address on error. */ char * ssh_remote_hostname(struct ssh *ssh) { struct sockaddr_storage from; socklen_t fromlen; struct addrinfo hints, *ai, *aitop; char name[NI_MAXHOST], ntop2[NI_MAXHOST]; const char *ntop = ssh_remote_ipaddr(ssh); /* Get IP address of client. */ fromlen = sizeof(from); memset(&from, 0, sizeof(from)); if (getpeername(ssh_packet_get_connection_in(ssh), (struct sockaddr *)&from, &fromlen) == -1) { debug_f("getpeername failed: %.100s", strerror(errno)); return xstrdup(ntop); } ipv64_normalise_mapped(&from, &fromlen); if (from.ss_family == AF_INET6) fromlen = sizeof(struct sockaddr_in6); debug3("trying to reverse map address %.100s.", ntop); /* Map the IP address to a host name. */ if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name), NULL, 0, NI_NAMEREQD) != 0) { /* Host name not found. Use ip address. */ return xstrdup(ntop); } /* * if reverse lookup result looks like a numeric hostname, * someone is trying to trick us by PTR record like following: * 1.1.1.10.in-addr.arpa. IN PTR 2.3.4.5 */ memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_DGRAM; /*dummy*/ hints.ai_flags = AI_NUMERICHOST; if (getaddrinfo(name, NULL, &hints, &ai) == 0) { logit("Nasty PTR record \"%s\" is set up for %s, ignoring", name, ntop); freeaddrinfo(ai); return xstrdup(ntop); } /* Names are stored in lowercase. */ lowercase(name); /* * Map it back to an IP address and check that the given * address actually is an address of this host. This is * necessary because anyone with access to a name server can * define arbitrary names for an IP address. Mapping from * name to IP address can be trusted better (but can still be * fooled if the intruder has access to the name server of * the domain). */ memset(&hints, 0, sizeof(hints)); hints.ai_family = from.ss_family; hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(name, NULL, &hints, &aitop) != 0) { logit("reverse mapping checking getaddrinfo for %.700s " "[%s] failed.", name, ntop); return xstrdup(ntop); } /* Look for the address from the list of addresses. */ for (ai = aitop; ai; ai = ai->ai_next) { if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2, sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 && (strcmp(ntop, ntop2) == 0)) break; } freeaddrinfo(aitop); /* If we reached the end of the list, the address was not there. */ if (ai == NULL) { /* Address not found for the host name. */ logit("Address %.100s maps to %.600s, but this does not " "map back to the address.", ntop, name); return xstrdup(ntop); } return xstrdup(name); } /* Returns the port number of the remote host. */ int ssh_remote_port(struct ssh *ssh) { (void)ssh_remote_ipaddr(ssh); /* Will lookup and cache. */ return ssh->remote_port; } /* * Returns the IP-address of the local host as a string. The returned * string must not be freed. */ const char * ssh_local_ipaddr(struct ssh *ssh) { (void)ssh_remote_ipaddr(ssh); /* Will lookup and cache. */ return ssh->local_ipaddr; } /* Returns the port number of the local host. */ int ssh_local_port(struct ssh *ssh) { (void)ssh_remote_ipaddr(ssh); /* Will lookup and cache. */ return ssh->local_port; } /* Returns the routing domain of the input socket, or NULL if unavailable */ const char * ssh_packet_rdomain_in(struct ssh *ssh) { if (ssh->rdomain_in != NULL) return ssh->rdomain_in; if (!ssh_packet_connection_is_on_socket(ssh)) return NULL; ssh->rdomain_in = get_rdomain(ssh->state->connection_in); return ssh->rdomain_in; } /* Closes the connection and clears and frees internal data structures. */ static void ssh_packet_close_internal(struct ssh *ssh, int do_close) { struct session_state *state = ssh->state; u_int mode; if (!state->initialized) return; state->initialized = 0; if (do_close) { if (state->connection_in == state->connection_out) { close(state->connection_out); } else { close(state->connection_in); close(state->connection_out); } } sshbuf_free(state->input); sshbuf_free(state->output); sshbuf_free(state->outgoing_packet); sshbuf_free(state->incoming_packet); for (mode = 0; mode < MODE_MAX; mode++) { kex_free_newkeys(state->newkeys[mode]); /* current keys */ state->newkeys[mode] = NULL; ssh_clear_newkeys(ssh, mode); /* next keys */ } #ifdef WITH_ZLIB /* compression state is in shared mem, so we can only release it once */ if (do_close && state->compression_buffer) { sshbuf_free(state->compression_buffer); if (state->compression_out_started) { z_streamp stream = &state->compression_out_stream; debug("compress outgoing: " "raw data %llu, compressed %llu, factor %.2f", (unsigned long long)stream->total_in, (unsigned long long)stream->total_out, stream->total_in == 0 ? 0.0 : (double) stream->total_out / stream->total_in); if (state->compression_out_failures == 0) deflateEnd(stream); } if (state->compression_in_started) { z_streamp stream = &state->compression_in_stream; debug("compress incoming: " "raw data %llu, compressed %llu, factor %.2f", (unsigned long long)stream->total_out, (unsigned long long)stream->total_in, stream->total_out == 0 ? 0.0 : (double) stream->total_in / stream->total_out); if (state->compression_in_failures == 0) inflateEnd(stream); } } #endif /* WITH_ZLIB */ cipher_free(state->send_context); cipher_free(state->receive_context); state->send_context = state->receive_context = NULL; if (do_close) { free(ssh->local_ipaddr); ssh->local_ipaddr = NULL; free(ssh->remote_ipaddr); ssh->remote_ipaddr = NULL; free(ssh->state); ssh->state = NULL; kex_free(ssh->kex); ssh->kex = NULL; } } void ssh_packet_close(struct ssh *ssh) { ssh_packet_close_internal(ssh, 1); } void ssh_packet_clear_keys(struct ssh *ssh) { ssh_packet_close_internal(ssh, 0); } /* Sets remote side protocol flags. */ void ssh_packet_set_protocol_flags(struct ssh *ssh, u_int protocol_flags) { ssh->state->remote_protocol_flags = protocol_flags; } /* Returns the remote protocol flags set earlier by the above function. */ u_int ssh_packet_get_protocol_flags(struct ssh *ssh) { return ssh->state->remote_protocol_flags; } /* * Starts packet compression from the next packet on in both directions. * Level is compression level 1 (fastest) - 9 (slow, best) as in gzip. */ static int ssh_packet_init_compression(struct ssh *ssh) { if (!ssh->state->compression_buffer && ((ssh->state->compression_buffer = sshbuf_new()) == NULL)) return SSH_ERR_ALLOC_FAIL; return 0; } #ifdef WITH_ZLIB static int start_compression_out(struct ssh *ssh, int level) { if (level < 1 || level > 9) return SSH_ERR_INVALID_ARGUMENT; debug("Enabling compression at level %d.", level); if (ssh->state->compression_out_started == 1) deflateEnd(&ssh->state->compression_out_stream); switch (deflateInit(&ssh->state->compression_out_stream, level)) { case Z_OK: ssh->state->compression_out_started = 1; break; case Z_MEM_ERROR: return SSH_ERR_ALLOC_FAIL; default: return SSH_ERR_INTERNAL_ERROR; } return 0; } static int start_compression_in(struct ssh *ssh) { if (ssh->state->compression_in_started == 1) inflateEnd(&ssh->state->compression_in_stream); switch (inflateInit(&ssh->state->compression_in_stream)) { case Z_OK: ssh->state->compression_in_started = 1; break; case Z_MEM_ERROR: return SSH_ERR_ALLOC_FAIL; default: return SSH_ERR_INTERNAL_ERROR; } return 0; } /* XXX remove need for separate compression buffer */ static int compress_buffer(struct ssh *ssh, struct sshbuf *in, struct sshbuf *out) { u_char buf[4096]; int r, status; if (ssh->state->compression_out_started != 1) return SSH_ERR_INTERNAL_ERROR; /* This case is not handled below. */ if (sshbuf_len(in) == 0) return 0; /* Input is the contents of the input buffer. */ if ((ssh->state->compression_out_stream.next_in = sshbuf_mutable_ptr(in)) == NULL) return SSH_ERR_INTERNAL_ERROR; ssh->state->compression_out_stream.avail_in = sshbuf_len(in); /* Loop compressing until deflate() returns with avail_out != 0. */ do { /* Set up fixed-size output buffer. */ ssh->state->compression_out_stream.next_out = buf; ssh->state->compression_out_stream.avail_out = sizeof(buf); /* Compress as much data into the buffer as possible. */ status = deflate(&ssh->state->compression_out_stream, Z_PARTIAL_FLUSH); switch (status) { case Z_MEM_ERROR: return SSH_ERR_ALLOC_FAIL; case Z_OK: /* Append compressed data to output_buffer. */ if ((r = sshbuf_put(out, buf, sizeof(buf) - ssh->state->compression_out_stream.avail_out)) != 0) return r; break; case Z_STREAM_ERROR: default: ssh->state->compression_out_failures++; return SSH_ERR_INVALID_FORMAT; } } while (ssh->state->compression_out_stream.avail_out == 0); return 0; } static int uncompress_buffer(struct ssh *ssh, struct sshbuf *in, struct sshbuf *out) { u_char buf[4096]; int r, status; if (ssh->state->compression_in_started != 1) return SSH_ERR_INTERNAL_ERROR; if ((ssh->state->compression_in_stream.next_in = sshbuf_mutable_ptr(in)) == NULL) return SSH_ERR_INTERNAL_ERROR; ssh->state->compression_in_stream.avail_in = sshbuf_len(in); for (;;) { /* Set up fixed-size output buffer. */ ssh->state->compression_in_stream.next_out = buf; ssh->state->compression_in_stream.avail_out = sizeof(buf); status = inflate(&ssh->state->compression_in_stream, Z_SYNC_FLUSH); switch (status) { case Z_OK: if ((r = sshbuf_put(out, buf, sizeof(buf) - ssh->state->compression_in_stream.avail_out)) != 0) return r; break; case Z_BUF_ERROR: /* * Comments in zlib.h say that we should keep calling * inflate() until we get an error. This appears to * be the error that we get. */ return 0; case Z_DATA_ERROR: return SSH_ERR_INVALID_FORMAT; case Z_MEM_ERROR: return SSH_ERR_ALLOC_FAIL; case Z_STREAM_ERROR: default: ssh->state->compression_in_failures++; return SSH_ERR_INTERNAL_ERROR; } } /* NOTREACHED */ } #else /* WITH_ZLIB */ static int start_compression_out(struct ssh *ssh, int level) { return SSH_ERR_INTERNAL_ERROR; } static int start_compression_in(struct ssh *ssh) { return SSH_ERR_INTERNAL_ERROR; } static int compress_buffer(struct ssh *ssh, struct sshbuf *in, struct sshbuf *out) { return SSH_ERR_INTERNAL_ERROR; } static int uncompress_buffer(struct ssh *ssh, struct sshbuf *in, struct sshbuf *out) { return SSH_ERR_INTERNAL_ERROR; } #endif /* WITH_ZLIB */ void ssh_clear_newkeys(struct ssh *ssh, int mode) { if (ssh->kex && ssh->kex->newkeys[mode]) { kex_free_newkeys(ssh->kex->newkeys[mode]); ssh->kex->newkeys[mode] = NULL; } } int ssh_set_newkeys(struct ssh *ssh, int mode) { struct session_state *state = ssh->state; struct sshenc *enc; struct sshmac *mac; struct sshcomp *comp; struct sshcipher_ctx **ccp; struct packet_state *ps; u_int64_t *max_blocks; const char *wmsg; int r, crypt_type; const char *dir = mode == MODE_OUT ? "out" : "in"; debug2_f("mode %d", mode); if (mode == MODE_OUT) { ccp = &state->send_context; crypt_type = CIPHER_ENCRYPT; ps = &state->p_send; max_blocks = &state->max_blocks_out; } else { ccp = &state->receive_context; crypt_type = CIPHER_DECRYPT; ps = &state->p_read; max_blocks = &state->max_blocks_in; } if (state->newkeys[mode] != NULL) { debug_f("rekeying %s, input %llu bytes %llu blocks, " "output %llu bytes %llu blocks", dir, (unsigned long long)state->p_read.bytes, (unsigned long long)state->p_read.blocks, (unsigned long long)state->p_send.bytes, (unsigned long long)state->p_send.blocks); kex_free_newkeys(state->newkeys[mode]); state->newkeys[mode] = NULL; } /* note that both bytes and the seqnr are not reset */ ps->packets = ps->blocks = 0; /* move newkeys from kex to state */ if ((state->newkeys[mode] = ssh->kex->newkeys[mode]) == NULL) return SSH_ERR_INTERNAL_ERROR; ssh->kex->newkeys[mode] = NULL; enc = &state->newkeys[mode]->enc; mac = &state->newkeys[mode]->mac; comp = &state->newkeys[mode]->comp; if (cipher_authlen(enc->cipher) == 0) { if ((r = mac_init(mac)) != 0) return r; } mac->enabled = 1; DBG(debug_f("cipher_init: %s", dir)); cipher_free(*ccp); *ccp = NULL; if ((r = cipher_init(ccp, enc->cipher, enc->key, enc->key_len, enc->iv, enc->iv_len, crypt_type)) != 0) return r; if (!state->cipher_warning_done && (wmsg = cipher_warning_message(*ccp)) != NULL) { error("Warning: %s", wmsg); state->cipher_warning_done = 1; } /* Deleting the keys does not gain extra security */ /* explicit_bzero(enc->iv, enc->block_size); explicit_bzero(enc->key, enc->key_len); explicit_bzero(mac->key, mac->key_len); */ if (((comp->type == COMP_DELAYED && state->after_authentication)) && comp->enabled == 0) { if ((r = ssh_packet_init_compression(ssh)) < 0) return r; if (mode == MODE_OUT) { if ((r = start_compression_out(ssh, 6)) != 0) return r; } else { if ((r = start_compression_in(ssh)) != 0) return r; } comp->enabled = 1; } /* * The 2^(blocksize*2) limit is too expensive for 3DES, * so enforce a 1GB limit for small blocksizes. * See RFC4344 section 3.2. */ if (enc->block_size >= 16) *max_blocks = (u_int64_t)1 << (enc->block_size*2); else *max_blocks = ((u_int64_t)1 << 30) / enc->block_size; if (state->rekey_limit) *max_blocks = MINIMUM(*max_blocks, state->rekey_limit / enc->block_size); debug("rekey %s after %llu blocks", dir, (unsigned long long)*max_blocks); return 0; } #define MAX_PACKETS (1U<<31) static int ssh_packet_need_rekeying(struct ssh *ssh, u_int outbound_packet_len) { struct session_state *state = ssh->state; u_int32_t out_blocks; /* XXX client can't cope with rekeying pre-auth */ if (!state->after_authentication) return 0; /* Haven't keyed yet or KEX in progress. */ if (ssh_packet_is_rekeying(ssh)) return 0; /* Peer can't rekey */ if (ssh->compat & SSH_BUG_NOREKEY) return 0; /* * Permit one packet in or out per rekey - this allows us to * make progress when rekey limits are very small. */ if (state->p_send.packets == 0 && state->p_read.packets == 0) return 0; /* Time-based rekeying */ if (state->rekey_interval != 0 && (int64_t)state->rekey_time + state->rekey_interval <= monotime()) return 1; /* * Always rekey when MAX_PACKETS sent in either direction * As per RFC4344 section 3.1 we do this after 2^31 packets. */ if (state->p_send.packets > MAX_PACKETS || state->p_read.packets > MAX_PACKETS) return 1; /* Rekey after (cipher-specific) maximum blocks */ out_blocks = ROUNDUP(outbound_packet_len, state->newkeys[MODE_OUT]->enc.block_size); return (state->max_blocks_out && (state->p_send.blocks + out_blocks > state->max_blocks_out)) || (state->max_blocks_in && (state->p_read.blocks > state->max_blocks_in)); } int ssh_packet_check_rekey(struct ssh *ssh) { if (!ssh_packet_need_rekeying(ssh, 0)) return 0; debug3_f("rekex triggered"); return kex_start_rekex(ssh); } /* * Delayed compression for SSH2 is enabled after authentication: * This happens on the server side after a SSH2_MSG_USERAUTH_SUCCESS is sent, * and on the client side after a SSH2_MSG_USERAUTH_SUCCESS is received. */ static int ssh_packet_enable_delayed_compress(struct ssh *ssh) { struct session_state *state = ssh->state; struct sshcomp *comp = NULL; int r, mode; /* * Remember that we are past the authentication step, so rekeying * with COMP_DELAYED will turn on compression immediately. */ state->after_authentication = 1; for (mode = 0; mode < MODE_MAX; mode++) { /* protocol error: USERAUTH_SUCCESS received before NEWKEYS */ if (state->newkeys[mode] == NULL) continue; comp = &state->newkeys[mode]->comp; if (comp && !comp->enabled && comp->type == COMP_DELAYED) { if ((r = ssh_packet_init_compression(ssh)) != 0) return r; if (mode == MODE_OUT) { if ((r = start_compression_out(ssh, 6)) != 0) return r; } else { if ((r = start_compression_in(ssh)) != 0) return r; } comp->enabled = 1; } } return 0; } /* Used to mute debug logging for noisy packet types */ int ssh_packet_log_type(u_char type) { switch (type) { case SSH2_MSG_PING: case SSH2_MSG_PONG: case SSH2_MSG_CHANNEL_DATA: case SSH2_MSG_CHANNEL_EXTENDED_DATA: case SSH2_MSG_CHANNEL_WINDOW_ADJUST: return 0; default: return 1; } } /* * Finalize packet in SSH2 format (compress, mac, encrypt, enqueue) */ int ssh_packet_send2_wrapped(struct ssh *ssh) { struct session_state *state = ssh->state; u_char type, *cp, macbuf[SSH_DIGEST_MAX_LENGTH]; u_char tmp, padlen, pad = 0; u_int authlen = 0, aadlen = 0; u_int len; struct sshenc *enc = NULL; struct sshmac *mac = NULL; struct sshcomp *comp = NULL; int r, block_size; if (state->newkeys[MODE_OUT] != NULL) { enc = &state->newkeys[MODE_OUT]->enc; mac = &state->newkeys[MODE_OUT]->mac; comp = &state->newkeys[MODE_OUT]->comp; /* disable mac for authenticated encryption */ if ((authlen = cipher_authlen(enc->cipher)) != 0) mac = NULL; } block_size = enc ? enc->block_size : 8; aadlen = (mac && mac->enabled && mac->etm) || authlen ? 4 : 0; type = (sshbuf_ptr(state->outgoing_packet))[5]; if (ssh_packet_log_type(type)) debug3("send packet: type %u", type); #ifdef PACKET_DEBUG fprintf(stderr, "plain: "); sshbuf_dump(state->outgoing_packet, stderr); #endif if (comp && comp->enabled) { len = sshbuf_len(state->outgoing_packet); /* skip header, compress only payload */ if ((r = sshbuf_consume(state->outgoing_packet, 5)) != 0) goto out; sshbuf_reset(state->compression_buffer); if ((r = compress_buffer(ssh, state->outgoing_packet, state->compression_buffer)) != 0) goto out; sshbuf_reset(state->outgoing_packet); if ((r = sshbuf_put(state->outgoing_packet, "\0\0\0\0\0", 5)) != 0 || (r = sshbuf_putb(state->outgoing_packet, state->compression_buffer)) != 0) goto out; DBG(debug("compression: raw %d compressed %zd", len, sshbuf_len(state->outgoing_packet))); } /* sizeof (packet_len + pad_len + payload) */ len = sshbuf_len(state->outgoing_packet); /* * calc size of padding, alloc space, get random data, * minimum padding is 4 bytes */ len -= aadlen; /* packet length is not encrypted for EtM modes */ padlen = block_size - (len % block_size); if (padlen < 4) padlen += block_size; if (state->extra_pad) { tmp = state->extra_pad; state->extra_pad = ROUNDUP(state->extra_pad, block_size); /* check if roundup overflowed */ if (state->extra_pad < tmp) return SSH_ERR_INVALID_ARGUMENT; tmp = (len + padlen) % state->extra_pad; /* Check whether pad calculation below will underflow */ if (tmp > state->extra_pad) return SSH_ERR_INVALID_ARGUMENT; pad = state->extra_pad - tmp; DBG(debug3_f("adding %d (len %d padlen %d extra_pad %d)", pad, len, padlen, state->extra_pad)); tmp = padlen; padlen += pad; /* Check whether padlen calculation overflowed */ if (padlen < tmp) return SSH_ERR_INVALID_ARGUMENT; /* overflow */ state->extra_pad = 0; } if ((r = sshbuf_reserve(state->outgoing_packet, padlen, &cp)) != 0) goto out; if (enc && !cipher_ctx_is_plaintext(state->send_context)) { /* random padding */ arc4random_buf(cp, padlen); } else { /* clear padding */ explicit_bzero(cp, padlen); } /* sizeof (packet_len + pad_len + payload + padding) */ len = sshbuf_len(state->outgoing_packet); cp = sshbuf_mutable_ptr(state->outgoing_packet); if (cp == NULL) { r = SSH_ERR_INTERNAL_ERROR; goto out; } /* packet_length includes payload, padding and padding length field */ POKE_U32(cp, len - 4); cp[4] = padlen; DBG(debug("send: len %d (includes padlen %d, aadlen %d)", len, padlen, aadlen)); /* compute MAC over seqnr and packet(length fields, payload, padding) */ if (mac && mac->enabled && !mac->etm) { if ((r = mac_compute(mac, state->p_send.seqnr, sshbuf_ptr(state->outgoing_packet), len, macbuf, sizeof(macbuf))) != 0) goto out; DBG(debug("done calc MAC out #%d", state->p_send.seqnr)); } /* encrypt packet and append to output buffer. */ if ((r = sshbuf_reserve(state->output, sshbuf_len(state->outgoing_packet) + authlen, &cp)) != 0) goto out; if ((r = cipher_crypt(state->send_context, state->p_send.seqnr, cp, sshbuf_ptr(state->outgoing_packet), len - aadlen, aadlen, authlen)) != 0) goto out; /* append unencrypted MAC */ if (mac && mac->enabled) { if (mac->etm) { /* EtM: compute mac over aadlen + cipher text */ if ((r = mac_compute(mac, state->p_send.seqnr, cp, len, macbuf, sizeof(macbuf))) != 0) goto out; DBG(debug("done calc MAC(EtM) out #%d", state->p_send.seqnr)); } if ((r = sshbuf_put(state->output, macbuf, mac->mac_len)) != 0) goto out; } #ifdef PACKET_DEBUG fprintf(stderr, "encrypted: "); sshbuf_dump(state->output, stderr); #endif /* increment sequence number for outgoing packets */ if (++state->p_send.seqnr == 0) { if ((ssh->kex->flags & KEX_INITIAL) != 0) { ssh_packet_disconnect(ssh, "outgoing sequence number " "wrapped during initial key exchange"); } logit("outgoing seqnr wraps around"); } if (++state->p_send.packets == 0) if (!(ssh->compat & SSH_BUG_NOREKEY)) return SSH_ERR_NEED_REKEY; state->p_send.blocks += len / block_size; state->p_send.bytes += len; sshbuf_reset(state->outgoing_packet); if (type == SSH2_MSG_NEWKEYS && ssh->kex->kex_strict) { debug_f("resetting send seqnr %u", state->p_send.seqnr); state->p_send.seqnr = 0; } if (type == SSH2_MSG_NEWKEYS) r = ssh_set_newkeys(ssh, MODE_OUT); else if (type == SSH2_MSG_USERAUTH_SUCCESS && state->server_side) r = ssh_packet_enable_delayed_compress(ssh); else r = 0; out: return r; } /* returns non-zero if the specified packet type is usec by KEX */ static int ssh_packet_type_is_kex(u_char type) { return type >= SSH2_MSG_TRANSPORT_MIN && type <= SSH2_MSG_TRANSPORT_MAX && type != SSH2_MSG_SERVICE_REQUEST && type != SSH2_MSG_SERVICE_ACCEPT && type != SSH2_MSG_EXT_INFO; } int ssh_packet_send2(struct ssh *ssh) { struct session_state *state = ssh->state; struct packet *p; u_char type; int r, need_rekey; if (sshbuf_len(state->outgoing_packet) < 6) return SSH_ERR_INTERNAL_ERROR; type = sshbuf_ptr(state->outgoing_packet)[5]; need_rekey = !ssh_packet_type_is_kex(type) && ssh_packet_need_rekeying(ssh, sshbuf_len(state->outgoing_packet)); /* * During rekeying we can only send key exchange messages. * Queue everything else. */ if ((need_rekey || state->rekeying) && !ssh_packet_type_is_kex(type)) { if (need_rekey) debug3_f("rekex triggered"); debug("enqueue packet: %u", type); p = calloc(1, sizeof(*p)); if (p == NULL) return SSH_ERR_ALLOC_FAIL; p->type = type; p->payload = state->outgoing_packet; TAILQ_INSERT_TAIL(&state->outgoing, p, next); state->outgoing_packet = sshbuf_new(); if (state->outgoing_packet == NULL) return SSH_ERR_ALLOC_FAIL; if (need_rekey) { /* * This packet triggered a rekey, so send the * KEXINIT now. * NB. reenters this function via kex_start_rekex(). */ return kex_start_rekex(ssh); } return 0; } /* rekeying starts with sending KEXINIT */ if (type == SSH2_MSG_KEXINIT) state->rekeying = 1; if ((r = ssh_packet_send2_wrapped(ssh)) != 0) return r; /* after a NEWKEYS message we can send the complete queue */ if (type == SSH2_MSG_NEWKEYS) { state->rekeying = 0; state->rekey_time = monotime(); while ((p = TAILQ_FIRST(&state->outgoing))) { type = p->type; /* * If this packet triggers a rekex, then skip the * remaining packets in the queue for now. * NB. re-enters this function via kex_start_rekex. */ if (ssh_packet_need_rekeying(ssh, sshbuf_len(p->payload))) { debug3_f("queued packet triggered rekex"); return kex_start_rekex(ssh); } debug("dequeue packet: %u", type); sshbuf_free(state->outgoing_packet); state->outgoing_packet = p->payload; TAILQ_REMOVE(&state->outgoing, p, next); memset(p, 0, sizeof(*p)); free(p); if ((r = ssh_packet_send2_wrapped(ssh)) != 0) return r; } } return 0; } /* * Waits until a packet has been received, and returns its type. Note that * no other data is processed until this returns, so this function should not * be used during the interactive session. */ int ssh_packet_read_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) { struct session_state *state = ssh->state; int len, r, ms_remain = 0; struct pollfd pfd; char buf[8192]; struct timeval start; struct timespec timespec, *timespecp = NULL; DBG(debug("packet_read()")); /* * Since we are blocking, ensure that all written packets have * been sent. */ if ((r = ssh_packet_write_wait(ssh)) != 0) goto out; /* Stay in the loop until we have received a complete packet. */ for (;;) { /* Try to read a packet from the buffer. */ if ((r = ssh_packet_read_poll_seqnr(ssh, typep, seqnr_p)) != 0) break; /* If we got a packet, return it. */ if (*typep != SSH_MSG_NONE) break; /* * Otherwise, wait for some data to arrive, add it to the * buffer, and try again. */ pfd.fd = state->connection_in; pfd.events = POLLIN; if (state->packet_timeout_ms > 0) { ms_remain = state->packet_timeout_ms; timespecp = ×pec; } /* Wait for some data to arrive. */ for (;;) { if (state->packet_timeout_ms > 0) { ms_to_timespec(×pec, ms_remain); monotime_tv(&start); } if ((r = ppoll(&pfd, 1, timespecp, NULL)) >= 0) break; if (errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK) { r = SSH_ERR_SYSTEM_ERROR; goto out; } if (state->packet_timeout_ms <= 0) continue; ms_subtract_diff(&start, &ms_remain); if (ms_remain <= 0) { r = 0; break; } } if (r == 0) { r = SSH_ERR_CONN_TIMEOUT; goto out; } /* Read data from the socket. */ len = read(state->connection_in, buf, sizeof(buf)); if (len == 0) { r = SSH_ERR_CONN_CLOSED; goto out; } if (len == -1) { r = SSH_ERR_SYSTEM_ERROR; goto out; } /* Append it to the buffer. */ if ((r = ssh_packet_process_incoming(ssh, buf, len)) != 0) goto out; } out: return r; } int ssh_packet_read(struct ssh *ssh) { u_char type; int r; if ((r = ssh_packet_read_seqnr(ssh, &type, NULL)) != 0) fatal_fr(r, "read"); return type; } static int ssh_packet_read_poll2_mux(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) { struct session_state *state = ssh->state; const u_char *cp; size_t need; int r; if (ssh->kex) return SSH_ERR_INTERNAL_ERROR; *typep = SSH_MSG_NONE; cp = sshbuf_ptr(state->input); if (state->packlen == 0) { if (sshbuf_len(state->input) < 4 + 1) return 0; /* packet is incomplete */ state->packlen = PEEK_U32(cp); if (state->packlen < 4 + 1 || state->packlen > PACKET_MAX_SIZE) return SSH_ERR_MESSAGE_INCOMPLETE; } need = state->packlen + 4; if (sshbuf_len(state->input) < need) return 0; /* packet is incomplete */ sshbuf_reset(state->incoming_packet); if ((r = sshbuf_put(state->incoming_packet, cp + 4, state->packlen)) != 0 || (r = sshbuf_consume(state->input, need)) != 0 || (r = sshbuf_get_u8(state->incoming_packet, NULL)) != 0 || (r = sshbuf_get_u8(state->incoming_packet, typep)) != 0) return r; if (ssh_packet_log_type(*typep)) debug3_f("type %u", *typep); /* sshbuf_dump(state->incoming_packet, stderr); */ /* reset for next packet */ state->packlen = 0; return r; } int ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) { struct session_state *state = ssh->state; u_int padlen, need; u_char *cp; u_int maclen, aadlen = 0, authlen = 0, block_size; struct sshenc *enc = NULL; struct sshmac *mac = NULL; struct sshcomp *comp = NULL; int r; if (state->mux) return ssh_packet_read_poll2_mux(ssh, typep, seqnr_p); *typep = SSH_MSG_NONE; if (state->packet_discard) return 0; if (state->newkeys[MODE_IN] != NULL) { enc = &state->newkeys[MODE_IN]->enc; mac = &state->newkeys[MODE_IN]->mac; comp = &state->newkeys[MODE_IN]->comp; /* disable mac for authenticated encryption */ if ((authlen = cipher_authlen(enc->cipher)) != 0) mac = NULL; } maclen = mac && mac->enabled ? mac->mac_len : 0; block_size = enc ? enc->block_size : 8; aadlen = (mac && mac->enabled && mac->etm) || authlen ? 4 : 0; if (aadlen && state->packlen == 0) { if (cipher_get_length(state->receive_context, &state->packlen, state->p_read.seqnr, sshbuf_ptr(state->input), sshbuf_len(state->input)) != 0) return 0; if (state->packlen < 1 + 4 || state->packlen > PACKET_MAX_SIZE) { #ifdef PACKET_DEBUG sshbuf_dump(state->input, stderr); #endif logit("Bad packet length %u.", state->packlen); if ((r = sshpkt_disconnect(ssh, "Packet corrupt")) != 0) return r; return SSH_ERR_CONN_CORRUPT; } sshbuf_reset(state->incoming_packet); } else if (state->packlen == 0) { /* * check if input size is less than the cipher block size, * decrypt first block and extract length of incoming packet */ if (sshbuf_len(state->input) < block_size) return 0; sshbuf_reset(state->incoming_packet); if ((r = sshbuf_reserve(state->incoming_packet, block_size, &cp)) != 0) goto out; if ((r = cipher_crypt(state->receive_context, state->p_send.seqnr, cp, sshbuf_ptr(state->input), block_size, 0, 0)) != 0) goto out; state->packlen = PEEK_U32(sshbuf_ptr(state->incoming_packet)); if (state->packlen < 1 + 4 || state->packlen > PACKET_MAX_SIZE) { #ifdef PACKET_DEBUG fprintf(stderr, "input: \n"); sshbuf_dump(state->input, stderr); fprintf(stderr, "incoming_packet: \n"); sshbuf_dump(state->incoming_packet, stderr); #endif logit("Bad packet length %u.", state->packlen); return ssh_packet_start_discard(ssh, enc, mac, 0, PACKET_MAX_SIZE); } if ((r = sshbuf_consume(state->input, block_size)) != 0) goto out; } DBG(debug("input: packet len %u", state->packlen+4)); if (aadlen) { /* only the payload is encrypted */ need = state->packlen; } else { /* * the payload size and the payload are encrypted, but we * have a partial packet of block_size bytes */ need = 4 + state->packlen - block_size; } DBG(debug("partial packet: block %d, need %d, maclen %d, authlen %d," " aadlen %d", block_size, need, maclen, authlen, aadlen)); if (need % block_size != 0) { logit("padding error: need %d block %d mod %d", need, block_size, need % block_size); return ssh_packet_start_discard(ssh, enc, mac, 0, PACKET_MAX_SIZE - block_size); } /* * check if the entire packet has been received and * decrypt into incoming_packet: * 'aadlen' bytes are unencrypted, but authenticated. * 'need' bytes are encrypted, followed by either * 'authlen' bytes of authentication tag or * 'maclen' bytes of message authentication code. */ if (sshbuf_len(state->input) < aadlen + need + authlen + maclen) return 0; /* packet is incomplete */ #ifdef PACKET_DEBUG fprintf(stderr, "read_poll enc/full: "); sshbuf_dump(state->input, stderr); #endif /* EtM: check mac over encrypted input */ if (mac && mac->enabled && mac->etm) { if ((r = mac_check(mac, state->p_read.seqnr, sshbuf_ptr(state->input), aadlen + need, sshbuf_ptr(state->input) + aadlen + need + authlen, maclen)) != 0) { if (r == SSH_ERR_MAC_INVALID) logit("Corrupted MAC on input."); goto out; } } if ((r = sshbuf_reserve(state->incoming_packet, aadlen + need, &cp)) != 0) goto out; if ((r = cipher_crypt(state->receive_context, state->p_read.seqnr, cp, sshbuf_ptr(state->input), need, aadlen, authlen)) != 0) goto out; if ((r = sshbuf_consume(state->input, aadlen + need + authlen)) != 0) goto out; if (mac && mac->enabled) { /* Not EtM: check MAC over cleartext */ if (!mac->etm && (r = mac_check(mac, state->p_read.seqnr, sshbuf_ptr(state->incoming_packet), sshbuf_len(state->incoming_packet), sshbuf_ptr(state->input), maclen)) != 0) { if (r != SSH_ERR_MAC_INVALID) goto out; logit("Corrupted MAC on input."); if (need + block_size > PACKET_MAX_SIZE) return SSH_ERR_INTERNAL_ERROR; return ssh_packet_start_discard(ssh, enc, mac, sshbuf_len(state->incoming_packet), PACKET_MAX_SIZE - need - block_size); } /* Remove MAC from input buffer */ DBG(debug("MAC #%d ok", state->p_read.seqnr)); if ((r = sshbuf_consume(state->input, mac->mac_len)) != 0) goto out; } if (seqnr_p != NULL) *seqnr_p = state->p_read.seqnr; if (++state->p_read.seqnr == 0) { if ((ssh->kex->flags & KEX_INITIAL) != 0) { ssh_packet_disconnect(ssh, "incoming sequence number " "wrapped during initial key exchange"); } logit("incoming seqnr wraps around"); } if (++state->p_read.packets == 0) if (!(ssh->compat & SSH_BUG_NOREKEY)) return SSH_ERR_NEED_REKEY; state->p_read.blocks += (state->packlen + 4) / block_size; state->p_read.bytes += state->packlen + 4; /* get padlen */ padlen = sshbuf_ptr(state->incoming_packet)[4]; DBG(debug("input: padlen %d", padlen)); if (padlen < 4) { if ((r = sshpkt_disconnect(ssh, "Corrupted padlen %d on input.", padlen)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) return r; return SSH_ERR_CONN_CORRUPT; } /* skip packet size + padlen, discard padding */ if ((r = sshbuf_consume(state->incoming_packet, 4 + 1)) != 0 || ((r = sshbuf_consume_end(state->incoming_packet, padlen)) != 0)) goto out; DBG(debug("input: len before de-compress %zd", sshbuf_len(state->incoming_packet))); if (comp && comp->enabled) { sshbuf_reset(state->compression_buffer); if ((r = uncompress_buffer(ssh, state->incoming_packet, state->compression_buffer)) != 0) goto out; sshbuf_reset(state->incoming_packet); if ((r = sshbuf_putb(state->incoming_packet, state->compression_buffer)) != 0) goto out; DBG(debug("input: len after de-compress %zd", sshbuf_len(state->incoming_packet))); } /* * get packet type, implies consume. * return length of payload (without type field) */ if ((r = sshbuf_get_u8(state->incoming_packet, typep)) != 0) goto out; if (ssh_packet_log_type(*typep)) debug3("receive packet: type %u", *typep); if (*typep < SSH2_MSG_MIN) { if ((r = sshpkt_disconnect(ssh, "Invalid ssh2 packet type: %d", *typep)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) return r; return SSH_ERR_PROTOCOL_ERROR; } if (state->hook_in != NULL && (r = state->hook_in(ssh, state->incoming_packet, typep, state->hook_in_ctx)) != 0) return r; if (*typep == SSH2_MSG_USERAUTH_SUCCESS && !state->server_side) r = ssh_packet_enable_delayed_compress(ssh); else r = 0; #ifdef PACKET_DEBUG fprintf(stderr, "read/plain[%d]:\r\n", *typep); sshbuf_dump(state->incoming_packet, stderr); #endif /* reset for next packet */ state->packlen = 0; if (*typep == SSH2_MSG_NEWKEYS && ssh->kex->kex_strict) { debug_f("resetting read seqnr %u", state->p_read.seqnr); state->p_read.seqnr = 0; } if ((r = ssh_packet_check_rekey(ssh)) != 0) return r; out: return r; } int ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) { struct session_state *state = ssh->state; u_int reason, seqnr; int r; u_char *msg; const u_char *d; size_t len; for (;;) { msg = NULL; r = ssh_packet_read_poll2(ssh, typep, seqnr_p); if (r != 0) return r; if (*typep == 0) { /* no message ready */ return 0; } state->keep_alive_timeouts = 0; DBG(debug("received packet type %d", *typep)); /* Always process disconnect messages */ if (*typep == SSH2_MSG_DISCONNECT) { if ((r = sshpkt_get_u32(ssh, &reason)) != 0 || (r = sshpkt_get_string(ssh, &msg, NULL)) != 0) return r; /* Ignore normal client exit notifications */ do_log2(ssh->state->server_side && reason == SSH2_DISCONNECT_BY_APPLICATION ? SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR, "Received disconnect from %s port %d:" "%u: %.400s", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), reason, msg); free(msg); return SSH_ERR_DISCONNECTED; } /* * Do not implicitly handle any messages here during initial * KEX when in strict mode. They will be need to be allowed * explicitly by the KEX dispatch table or they will generate * protocol errors. */ if (ssh->kex != NULL && (ssh->kex->flags & KEX_INITIAL) && ssh->kex->kex_strict) return 0; /* Implicitly handle transport-level messages */ switch (*typep) { case SSH2_MSG_IGNORE: debug3("Received SSH2_MSG_IGNORE"); break; case SSH2_MSG_DEBUG: if ((r = sshpkt_get_u8(ssh, NULL)) != 0 || (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 || (r = sshpkt_get_string(ssh, NULL, NULL)) != 0) { free(msg); return r; } debug("Remote: %.900s", msg); free(msg); break; case SSH2_MSG_UNIMPLEMENTED: if ((r = sshpkt_get_u32(ssh, &seqnr)) != 0) return r; debug("Received SSH2_MSG_UNIMPLEMENTED for %u", seqnr); break; case SSH2_MSG_PING: if ((r = sshpkt_get_string_direct(ssh, &d, &len)) != 0) return r; DBG(debug("Received SSH2_MSG_PING len %zu", len)); if (!ssh->state->after_authentication) { DBG(debug("Won't reply to PING in preauth")); break; } if (ssh_packet_is_rekeying(ssh)) { DBG(debug("Won't reply to PING during KEX")); break; } if ((r = sshpkt_start(ssh, SSH2_MSG_PONG)) != 0 || (r = sshpkt_put_string(ssh, d, len)) != 0 || (r = sshpkt_send(ssh)) != 0) return r; break; case SSH2_MSG_PONG: if ((r = sshpkt_get_string_direct(ssh, NULL, &len)) != 0) return r; DBG(debug("Received SSH2_MSG_PONG len %zu", len)); break; default: return 0; } } } /* * Buffers the supplied input data. This is intended to be used together * with packet_read_poll(). */ int ssh_packet_process_incoming(struct ssh *ssh, const char *buf, u_int len) { struct session_state *state = ssh->state; int r; if (state->packet_discard) { state->keep_alive_timeouts = 0; /* ?? */ if (len >= state->packet_discard) { if ((r = ssh_packet_stop_discard(ssh)) != 0) return r; } state->packet_discard -= len; return 0; } if ((r = sshbuf_put(state->input, buf, len)) != 0) return r; return 0; } /* Reads and buffers data from the specified fd */ int ssh_packet_process_read(struct ssh *ssh, int fd) { struct session_state *state = ssh->state; int r; size_t rlen; if ((r = sshbuf_read(fd, state->input, PACKET_MAX_SIZE, &rlen)) != 0) return r; if (state->packet_discard) { if ((r = sshbuf_consume_end(state->input, rlen)) != 0) return r; state->keep_alive_timeouts = 0; /* ?? */ if (rlen >= state->packet_discard) { if ((r = ssh_packet_stop_discard(ssh)) != 0) return r; } state->packet_discard -= rlen; return 0; } return 0; } int ssh_packet_remaining(struct ssh *ssh) { return sshbuf_len(ssh->state->incoming_packet); } /* * Sends a diagnostic message from the server to the client. This message * can be sent at any time (but not while constructing another message). The * message is printed immediately, but only if the client is being executed * in verbose mode. These messages are primarily intended to ease debugging * authentication problems. The length of the formatted message must not * exceed 1024 bytes. This will automatically call ssh_packet_write_wait. */ void ssh_packet_send_debug(struct ssh *ssh, const char *fmt,...) { char buf[1024]; va_list args; int r; if ((ssh->compat & SSH_BUG_DEBUG)) return; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); debug3("sending debug message: %s", buf); if ((r = sshpkt_start(ssh, SSH2_MSG_DEBUG)) != 0 || (r = sshpkt_put_u8(ssh, 0)) != 0 || /* always display */ (r = sshpkt_put_cstring(ssh, buf)) != 0 || (r = sshpkt_put_cstring(ssh, "")) != 0 || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) fatal_fr(r, "send DEBUG"); } void sshpkt_fmt_connection_id(struct ssh *ssh, char *s, size_t l) { snprintf(s, l, "%.200s%s%s port %d", ssh->log_preamble ? ssh->log_preamble : "", ssh->log_preamble ? " " : "", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); } /* * Pretty-print connection-terminating errors and exit. */ static void sshpkt_vfatal(struct ssh *ssh, int r, const char *fmt, va_list ap) { char *tag = NULL, remote_id[512]; int oerrno = errno; sshpkt_fmt_connection_id(ssh, remote_id, sizeof(remote_id)); switch (r) { case SSH_ERR_CONN_CLOSED: ssh_packet_clear_keys(ssh); logdie("Connection closed by %s", remote_id); case SSH_ERR_CONN_TIMEOUT: ssh_packet_clear_keys(ssh); logdie("Connection %s %s timed out", ssh->state->server_side ? "from" : "to", remote_id); case SSH_ERR_DISCONNECTED: ssh_packet_clear_keys(ssh); logdie("Disconnected from %s", remote_id); case SSH_ERR_SYSTEM_ERROR: if (errno == ECONNRESET) { ssh_packet_clear_keys(ssh); logdie("Connection reset by %s", remote_id); } /* FALLTHROUGH */ case SSH_ERR_NO_CIPHER_ALG_MATCH: case SSH_ERR_NO_MAC_ALG_MATCH: case SSH_ERR_NO_COMPRESS_ALG_MATCH: case SSH_ERR_NO_KEX_ALG_MATCH: case SSH_ERR_NO_HOSTKEY_ALG_MATCH: if (ssh->kex && ssh->kex->failed_choice) { - BLACKLIST_NOTIFY(ssh, BLACKLIST_AUTH_FAIL, "ssh"); ssh_packet_clear_keys(ssh); errno = oerrno; logdie("Unable to negotiate with %s: %s. " "Their offer: %s", remote_id, ssh_err(r), ssh->kex->failed_choice); } /* FALLTHROUGH */ default: if (vasprintf(&tag, fmt, ap) == -1) { ssh_packet_clear_keys(ssh); logdie_f("could not allocate failure message"); } ssh_packet_clear_keys(ssh); errno = oerrno; logdie_r(r, "%s%sConnection %s %s", tag != NULL ? tag : "", tag != NULL ? ": " : "", ssh->state->server_side ? "from" : "to", remote_id); } } void sshpkt_fatal(struct ssh *ssh, int r, const char *fmt, ...) { va_list ap; va_start(ap, fmt); sshpkt_vfatal(ssh, r, fmt, ap); /* NOTREACHED */ va_end(ap); logdie_f("should have exited"); } /* * Logs the error plus constructs and sends a disconnect packet, closes the * connection, and exits. This function never returns. The error message * should not contain a newline. The length of the formatted message must * not exceed 1024 bytes. */ void ssh_packet_disconnect(struct ssh *ssh, const char *fmt,...) { char buf[1024], remote_id[512]; va_list args; static int disconnecting = 0; int r; if (disconnecting) /* Guard against recursive invocations. */ fatal("packet_disconnect called recursively."); disconnecting = 1; /* * Format the message. Note that the caller must make sure the * message is of limited size. */ sshpkt_fmt_connection_id(ssh, remote_id, sizeof(remote_id)); va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); /* Display the error locally */ logit("Disconnecting %s: %.100s", remote_id, buf); /* * Send the disconnect message to the other side, and wait * for it to get sent. */ if ((r = sshpkt_disconnect(ssh, "%s", buf)) != 0) sshpkt_fatal(ssh, r, "%s", __func__); if ((r = ssh_packet_write_wait(ssh)) != 0) sshpkt_fatal(ssh, r, "%s", __func__); /* Close the connection. */ ssh_packet_close(ssh); cleanup_exit(255); } /* * Checks if there is any buffered output, and tries to write some of * the output. */ int ssh_packet_write_poll(struct ssh *ssh) { struct session_state *state = ssh->state; int len = sshbuf_len(state->output); int r; if (len > 0) { len = write(state->connection_out, sshbuf_ptr(state->output), len); if (len == -1) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) return 0; return SSH_ERR_SYSTEM_ERROR; } if (len == 0) return SSH_ERR_CONN_CLOSED; if ((r = sshbuf_consume(state->output, len)) != 0) return r; } return 0; } /* * Calls packet_write_poll repeatedly until all pending output data has been * written. */ int ssh_packet_write_wait(struct ssh *ssh) { int ret, r, ms_remain = 0; struct timeval start; struct timespec timespec, *timespecp = NULL; struct session_state *state = ssh->state; struct pollfd pfd; if ((r = ssh_packet_write_poll(ssh)) != 0) return r; while (ssh_packet_have_data_to_write(ssh)) { pfd.fd = state->connection_out; pfd.events = POLLOUT; if (state->packet_timeout_ms > 0) { ms_remain = state->packet_timeout_ms; timespecp = ×pec; } for (;;) { if (state->packet_timeout_ms > 0) { ms_to_timespec(×pec, ms_remain); monotime_tv(&start); } if ((ret = ppoll(&pfd, 1, timespecp, NULL)) >= 0) break; if (errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK) break; if (state->packet_timeout_ms <= 0) continue; ms_subtract_diff(&start, &ms_remain); if (ms_remain <= 0) { ret = 0; break; } } if (ret == 0) return SSH_ERR_CONN_TIMEOUT; if ((r = ssh_packet_write_poll(ssh)) != 0) return r; } return 0; } /* Returns true if there is buffered data to write to the connection. */ int ssh_packet_have_data_to_write(struct ssh *ssh) { return sshbuf_len(ssh->state->output) != 0; } /* Returns true if there is not too much data to write to the connection. */ int ssh_packet_not_very_much_data_to_write(struct ssh *ssh) { if (ssh->state->interactive_mode) return sshbuf_len(ssh->state->output) < 16384; else return sshbuf_len(ssh->state->output) < 128 * 1024; } /* * returns true when there are at most a few keystrokes of data to write * and the connection is in interactive mode. */ int ssh_packet_interactive_data_to_write(struct ssh *ssh) { return ssh->state->interactive_mode && sshbuf_len(ssh->state->output) < 256; } void ssh_packet_set_tos(struct ssh *ssh, int tos) { if (!ssh_packet_connection_is_on_socket(ssh) || tos == INT_MAX) return; set_sock_tos(ssh->state->connection_in, tos); } /* Informs that the current session is interactive. Sets IP flags for that. */ void ssh_packet_set_interactive(struct ssh *ssh, int interactive, int qos_interactive, int qos_bulk) { struct session_state *state = ssh->state; if (state->set_interactive_called) return; state->set_interactive_called = 1; /* Record that we are in interactive mode. */ state->interactive_mode = interactive; /* Only set socket options if using a socket. */ if (!ssh_packet_connection_is_on_socket(ssh)) return; set_nodelay(state->connection_in); ssh_packet_set_tos(ssh, interactive ? qos_interactive : qos_bulk); } /* Returns true if the current connection is interactive. */ int ssh_packet_is_interactive(struct ssh *ssh) { return ssh->state->interactive_mode; } int ssh_packet_set_maxsize(struct ssh *ssh, u_int s) { struct session_state *state = ssh->state; if (state->set_maxsize_called) { logit_f("called twice: old %d new %d", state->max_packet_size, s); return -1; } if (s < 4 * 1024 || s > 1024 * 1024) { logit_f("bad size %d", s); return -1; } state->set_maxsize_called = 1; debug_f("setting to %d", s); state->max_packet_size = s; return s; } int ssh_packet_inc_alive_timeouts(struct ssh *ssh) { return ++ssh->state->keep_alive_timeouts; } void ssh_packet_set_alive_timeouts(struct ssh *ssh, int ka) { ssh->state->keep_alive_timeouts = ka; } u_int ssh_packet_get_maxsize(struct ssh *ssh) { return ssh->state->max_packet_size; } void ssh_packet_set_rekey_limits(struct ssh *ssh, u_int64_t bytes, u_int32_t seconds) { debug3("rekey after %llu bytes, %u seconds", (unsigned long long)bytes, (unsigned int)seconds); ssh->state->rekey_limit = bytes; ssh->state->rekey_interval = seconds; } time_t ssh_packet_get_rekey_timeout(struct ssh *ssh) { time_t seconds; seconds = ssh->state->rekey_time + ssh->state->rekey_interval - monotime(); return (seconds <= 0 ? 1 : seconds); } void ssh_packet_set_server(struct ssh *ssh) { ssh->state->server_side = 1; ssh->kex->server = 1; /* XXX unify? */ } void ssh_packet_set_authenticated(struct ssh *ssh) { ssh->state->after_authentication = 1; } void * ssh_packet_get_input(struct ssh *ssh) { return (void *)ssh->state->input; } void * ssh_packet_get_output(struct ssh *ssh) { return (void *)ssh->state->output; } /* Reset after_authentication and reset compression in post-auth privsep */ static int ssh_packet_set_postauth(struct ssh *ssh) { int r; debug_f("called"); /* This was set in net child, but is not visible in user child */ ssh->state->after_authentication = 1; ssh->state->rekeying = 0; if ((r = ssh_packet_enable_delayed_compress(ssh)) != 0) return r; return 0; } /* Packet state (de-)serialization for privsep */ /* turn kex into a blob for packet state serialization */ static int kex_to_blob(struct sshbuf *m, struct kex *kex) { int r; if ((r = sshbuf_put_u32(m, kex->we_need)) != 0 || (r = sshbuf_put_cstring(m, kex->hostkey_alg)) != 0 || (r = sshbuf_put_u32(m, kex->hostkey_type)) != 0 || (r = sshbuf_put_u32(m, kex->hostkey_nid)) != 0 || (r = sshbuf_put_u32(m, kex->kex_type)) != 0 || (r = sshbuf_put_u32(m, kex->kex_strict)) != 0 || (r = sshbuf_put_stringb(m, kex->my)) != 0 || (r = sshbuf_put_stringb(m, kex->peer)) != 0 || (r = sshbuf_put_stringb(m, kex->client_version)) != 0 || (r = sshbuf_put_stringb(m, kex->server_version)) != 0 || (r = sshbuf_put_stringb(m, kex->session_id)) != 0 || (r = sshbuf_put_u32(m, kex->flags)) != 0) return r; return 0; } /* turn key exchange results into a blob for packet state serialization */ static int newkeys_to_blob(struct sshbuf *m, struct ssh *ssh, int mode) { struct sshbuf *b; struct sshcipher_ctx *cc; struct sshcomp *comp; struct sshenc *enc; struct sshmac *mac; struct newkeys *newkey; int r; if ((newkey = ssh->state->newkeys[mode]) == NULL) return SSH_ERR_INTERNAL_ERROR; enc = &newkey->enc; mac = &newkey->mac; comp = &newkey->comp; cc = (mode == MODE_OUT) ? ssh->state->send_context : ssh->state->receive_context; if ((r = cipher_get_keyiv(cc, enc->iv, enc->iv_len)) != 0) return r; if ((b = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; if ((r = sshbuf_put_cstring(b, enc->name)) != 0 || (r = sshbuf_put_u32(b, enc->enabled)) != 0 || (r = sshbuf_put_u32(b, enc->block_size)) != 0 || (r = sshbuf_put_string(b, enc->key, enc->key_len)) != 0 || (r = sshbuf_put_string(b, enc->iv, enc->iv_len)) != 0) goto out; if (cipher_authlen(enc->cipher) == 0) { if ((r = sshbuf_put_cstring(b, mac->name)) != 0 || (r = sshbuf_put_u32(b, mac->enabled)) != 0 || (r = sshbuf_put_string(b, mac->key, mac->key_len)) != 0) goto out; } if ((r = sshbuf_put_u32(b, comp->type)) != 0 || (r = sshbuf_put_cstring(b, comp->name)) != 0) goto out; r = sshbuf_put_stringb(m, b); out: sshbuf_free(b); return r; } /* serialize packet state into a blob */ int ssh_packet_get_state(struct ssh *ssh, struct sshbuf *m) { struct session_state *state = ssh->state; int r; if ((r = kex_to_blob(m, ssh->kex)) != 0 || (r = newkeys_to_blob(m, ssh, MODE_OUT)) != 0 || (r = newkeys_to_blob(m, ssh, MODE_IN)) != 0 || (r = sshbuf_put_u64(m, state->rekey_limit)) != 0 || (r = sshbuf_put_u32(m, state->rekey_interval)) != 0 || (r = sshbuf_put_u32(m, state->p_send.seqnr)) != 0 || (r = sshbuf_put_u64(m, state->p_send.blocks)) != 0 || (r = sshbuf_put_u32(m, state->p_send.packets)) != 0 || (r = sshbuf_put_u64(m, state->p_send.bytes)) != 0 || (r = sshbuf_put_u32(m, state->p_read.seqnr)) != 0 || (r = sshbuf_put_u64(m, state->p_read.blocks)) != 0 || (r = sshbuf_put_u32(m, state->p_read.packets)) != 0 || (r = sshbuf_put_u64(m, state->p_read.bytes)) != 0 || (r = sshbuf_put_stringb(m, state->input)) != 0 || (r = sshbuf_put_stringb(m, state->output)) != 0) return r; return 0; } /* restore key exchange results from blob for packet state de-serialization */ static int newkeys_from_blob(struct sshbuf *m, struct ssh *ssh, int mode) { struct sshbuf *b = NULL; struct sshcomp *comp; struct sshenc *enc; struct sshmac *mac; struct newkeys *newkey = NULL; size_t keylen, ivlen, maclen; int r; if ((newkey = calloc(1, sizeof(*newkey))) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if ((r = sshbuf_froms(m, &b)) != 0) goto out; #ifdef DEBUG_PK sshbuf_dump(b, stderr); #endif enc = &newkey->enc; mac = &newkey->mac; comp = &newkey->comp; if ((r = sshbuf_get_cstring(b, &enc->name, NULL)) != 0 || (r = sshbuf_get_u32(b, (u_int *)&enc->enabled)) != 0 || (r = sshbuf_get_u32(b, &enc->block_size)) != 0 || (r = sshbuf_get_string(b, &enc->key, &keylen)) != 0 || (r = sshbuf_get_string(b, &enc->iv, &ivlen)) != 0) goto out; if ((enc->cipher = cipher_by_name(enc->name)) == NULL) { r = SSH_ERR_INVALID_FORMAT; goto out; } if (cipher_authlen(enc->cipher) == 0) { if ((r = sshbuf_get_cstring(b, &mac->name, NULL)) != 0) goto out; if ((r = mac_setup(mac, mac->name)) != 0) goto out; if ((r = sshbuf_get_u32(b, (u_int *)&mac->enabled)) != 0 || (r = sshbuf_get_string(b, &mac->key, &maclen)) != 0) goto out; if (maclen > mac->key_len) { r = SSH_ERR_INVALID_FORMAT; goto out; } mac->key_len = maclen; } if ((r = sshbuf_get_u32(b, &comp->type)) != 0 || (r = sshbuf_get_cstring(b, &comp->name, NULL)) != 0) goto out; if (sshbuf_len(b) != 0) { r = SSH_ERR_INVALID_FORMAT; goto out; } enc->key_len = keylen; enc->iv_len = ivlen; ssh->kex->newkeys[mode] = newkey; newkey = NULL; r = 0; out: free(newkey); sshbuf_free(b); return r; } /* restore kex from blob for packet state de-serialization */ static int kex_from_blob(struct sshbuf *m, struct kex **kexp) { struct kex *kex; int r; if ((kex = kex_new()) == NULL) return SSH_ERR_ALLOC_FAIL; if ((r = sshbuf_get_u32(m, &kex->we_need)) != 0 || (r = sshbuf_get_cstring(m, &kex->hostkey_alg, NULL)) != 0 || (r = sshbuf_get_u32(m, (u_int *)&kex->hostkey_type)) != 0 || (r = sshbuf_get_u32(m, (u_int *)&kex->hostkey_nid)) != 0 || (r = sshbuf_get_u32(m, &kex->kex_type)) != 0 || (r = sshbuf_get_u32(m, &kex->kex_strict)) != 0 || (r = sshbuf_get_stringb(m, kex->my)) != 0 || (r = sshbuf_get_stringb(m, kex->peer)) != 0 || (r = sshbuf_get_stringb(m, kex->client_version)) != 0 || (r = sshbuf_get_stringb(m, kex->server_version)) != 0 || (r = sshbuf_get_stringb(m, kex->session_id)) != 0 || (r = sshbuf_get_u32(m, &kex->flags)) != 0) goto out; kex->server = 1; kex->done = 1; r = 0; out: if (r != 0 || kexp == NULL) { kex_free(kex); if (kexp != NULL) *kexp = NULL; } else { kex_free(*kexp); *kexp = kex; } return r; } /* * Restore packet state from content of blob 'm' (de-serialization). * Note that 'm' will be partially consumed on parsing or any other errors. */ int ssh_packet_set_state(struct ssh *ssh, struct sshbuf *m) { struct session_state *state = ssh->state; const u_char *input, *output; size_t ilen, olen; int r; if ((r = kex_from_blob(m, &ssh->kex)) != 0 || (r = newkeys_from_blob(m, ssh, MODE_OUT)) != 0 || (r = newkeys_from_blob(m, ssh, MODE_IN)) != 0 || (r = sshbuf_get_u64(m, &state->rekey_limit)) != 0 || (r = sshbuf_get_u32(m, &state->rekey_interval)) != 0 || (r = sshbuf_get_u32(m, &state->p_send.seqnr)) != 0 || (r = sshbuf_get_u64(m, &state->p_send.blocks)) != 0 || (r = sshbuf_get_u32(m, &state->p_send.packets)) != 0 || (r = sshbuf_get_u64(m, &state->p_send.bytes)) != 0 || (r = sshbuf_get_u32(m, &state->p_read.seqnr)) != 0 || (r = sshbuf_get_u64(m, &state->p_read.blocks)) != 0 || (r = sshbuf_get_u32(m, &state->p_read.packets)) != 0 || (r = sshbuf_get_u64(m, &state->p_read.bytes)) != 0) return r; /* * We set the time here so that in post-auth privsep child we * count from the completion of the authentication. */ state->rekey_time = monotime(); /* XXX ssh_set_newkeys overrides p_read.packets? XXX */ if ((r = ssh_set_newkeys(ssh, MODE_IN)) != 0 || (r = ssh_set_newkeys(ssh, MODE_OUT)) != 0) return r; if ((r = ssh_packet_set_postauth(ssh)) != 0) return r; sshbuf_reset(state->input); sshbuf_reset(state->output); if ((r = sshbuf_get_string_direct(m, &input, &ilen)) != 0 || (r = sshbuf_get_string_direct(m, &output, &olen)) != 0 || (r = sshbuf_put(state->input, input, ilen)) != 0 || (r = sshbuf_put(state->output, output, olen)) != 0) return r; if (sshbuf_len(m)) return SSH_ERR_INVALID_FORMAT; debug3_f("done"); return 0; } /* NEW API */ /* put data to the outgoing packet */ int sshpkt_put(struct ssh *ssh, const void *v, size_t len) { return sshbuf_put(ssh->state->outgoing_packet, v, len); } int sshpkt_putb(struct ssh *ssh, const struct sshbuf *b) { return sshbuf_putb(ssh->state->outgoing_packet, b); } int sshpkt_put_u8(struct ssh *ssh, u_char val) { return sshbuf_put_u8(ssh->state->outgoing_packet, val); } int sshpkt_put_u32(struct ssh *ssh, u_int32_t val) { return sshbuf_put_u32(ssh->state->outgoing_packet, val); } int sshpkt_put_u64(struct ssh *ssh, u_int64_t val) { return sshbuf_put_u64(ssh->state->outgoing_packet, val); } int sshpkt_put_string(struct ssh *ssh, const void *v, size_t len) { return sshbuf_put_string(ssh->state->outgoing_packet, v, len); } int sshpkt_put_cstring(struct ssh *ssh, const void *v) { return sshbuf_put_cstring(ssh->state->outgoing_packet, v); } int sshpkt_put_stringb(struct ssh *ssh, const struct sshbuf *v) { return sshbuf_put_stringb(ssh->state->outgoing_packet, v); } #ifdef WITH_OPENSSL #ifdef OPENSSL_HAS_ECC int sshpkt_put_ec(struct ssh *ssh, const EC_POINT *v, const EC_GROUP *g) { return sshbuf_put_ec(ssh->state->outgoing_packet, v, g); } int sshpkt_put_ec_pkey(struct ssh *ssh, EVP_PKEY *pkey) { return sshbuf_put_ec_pkey(ssh->state->outgoing_packet, pkey); } #endif /* OPENSSL_HAS_ECC */ int sshpkt_put_bignum2(struct ssh *ssh, const BIGNUM *v) { return sshbuf_put_bignum2(ssh->state->outgoing_packet, v); } #endif /* WITH_OPENSSL */ /* fetch data from the incoming packet */ int sshpkt_get(struct ssh *ssh, void *valp, size_t len) { return sshbuf_get(ssh->state->incoming_packet, valp, len); } int sshpkt_get_u8(struct ssh *ssh, u_char *valp) { return sshbuf_get_u8(ssh->state->incoming_packet, valp); } int sshpkt_get_u32(struct ssh *ssh, u_int32_t *valp) { return sshbuf_get_u32(ssh->state->incoming_packet, valp); } int sshpkt_get_u64(struct ssh *ssh, u_int64_t *valp) { return sshbuf_get_u64(ssh->state->incoming_packet, valp); } int sshpkt_get_string(struct ssh *ssh, u_char **valp, size_t *lenp) { return sshbuf_get_string(ssh->state->incoming_packet, valp, lenp); } int sshpkt_get_string_direct(struct ssh *ssh, const u_char **valp, size_t *lenp) { return sshbuf_get_string_direct(ssh->state->incoming_packet, valp, lenp); } int sshpkt_peek_string_direct(struct ssh *ssh, const u_char **valp, size_t *lenp) { return sshbuf_peek_string_direct(ssh->state->incoming_packet, valp, lenp); } int sshpkt_get_cstring(struct ssh *ssh, char **valp, size_t *lenp) { return sshbuf_get_cstring(ssh->state->incoming_packet, valp, lenp); } int sshpkt_getb_froms(struct ssh *ssh, struct sshbuf **valp) { return sshbuf_froms(ssh->state->incoming_packet, valp); } #ifdef WITH_OPENSSL #ifdef OPENSSL_HAS_ECC int sshpkt_get_ec(struct ssh *ssh, EC_POINT *v, const EC_GROUP *g) { return sshbuf_get_ec(ssh->state->incoming_packet, v, g); } #endif /* OPENSSL_HAS_ECC */ int sshpkt_get_bignum2(struct ssh *ssh, BIGNUM **valp) { return sshbuf_get_bignum2(ssh->state->incoming_packet, valp); } #endif /* WITH_OPENSSL */ int sshpkt_get_end(struct ssh *ssh) { if (sshbuf_len(ssh->state->incoming_packet) > 0) return SSH_ERR_UNEXPECTED_TRAILING_DATA; return 0; } const u_char * sshpkt_ptr(struct ssh *ssh, size_t *lenp) { if (lenp != NULL) *lenp = sshbuf_len(ssh->state->incoming_packet); return sshbuf_ptr(ssh->state->incoming_packet); } /* start a new packet */ int sshpkt_start(struct ssh *ssh, u_char type) { u_char buf[6]; /* u32 packet length, u8 pad len, u8 type */ DBG(debug("packet_start[%d]", type)); memset(buf, 0, sizeof(buf)); buf[sizeof(buf) - 1] = type; sshbuf_reset(ssh->state->outgoing_packet); return sshbuf_put(ssh->state->outgoing_packet, buf, sizeof(buf)); } static int ssh_packet_send_mux(struct ssh *ssh) { struct session_state *state = ssh->state; u_char type, *cp; size_t len; int r; if (ssh->kex) return SSH_ERR_INTERNAL_ERROR; len = sshbuf_len(state->outgoing_packet); if (len < 6) return SSH_ERR_INTERNAL_ERROR; cp = sshbuf_mutable_ptr(state->outgoing_packet); type = cp[5]; if (ssh_packet_log_type(type)) debug3_f("type %u", type); /* drop everything, but the connection protocol */ if (type >= SSH2_MSG_CONNECTION_MIN && type <= SSH2_MSG_CONNECTION_MAX) { POKE_U32(cp, len - 4); if ((r = sshbuf_putb(state->output, state->outgoing_packet)) != 0) return r; /* sshbuf_dump(state->output, stderr); */ } sshbuf_reset(state->outgoing_packet); return 0; } /* * 9.2. Ignored Data Message * * byte SSH_MSG_IGNORE * string data * * All implementations MUST understand (and ignore) this message at any * time (after receiving the protocol version). No implementation is * required to send them. This message can be used as an additional * protection measure against advanced traffic analysis techniques. */ int sshpkt_msg_ignore(struct ssh *ssh, u_int nbytes) { u_int32_t rnd = 0; int r; u_int i; if ((r = sshpkt_start(ssh, SSH2_MSG_IGNORE)) != 0 || (r = sshpkt_put_u32(ssh, nbytes)) != 0) return r; for (i = 0; i < nbytes; i++) { if (i % 4 == 0) rnd = arc4random(); if ((r = sshpkt_put_u8(ssh, (u_char)rnd & 0xff)) != 0) return r; rnd >>= 8; } return 0; } /* send it */ int sshpkt_send(struct ssh *ssh) { if (ssh->state && ssh->state->mux) return ssh_packet_send_mux(ssh); return ssh_packet_send2(ssh); } int sshpkt_disconnect(struct ssh *ssh, const char *fmt,...) { char buf[1024]; va_list args; int r; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); debug2_f("sending SSH2_MSG_DISCONNECT: %s", buf); if ((r = sshpkt_start(ssh, SSH2_MSG_DISCONNECT)) != 0 || (r = sshpkt_put_u32(ssh, SSH2_DISCONNECT_PROTOCOL_ERROR)) != 0 || (r = sshpkt_put_cstring(ssh, buf)) != 0 || (r = sshpkt_put_cstring(ssh, "")) != 0 || (r = sshpkt_send(ssh)) != 0) return r; return 0; } /* roundup current message to pad bytes */ int sshpkt_add_padding(struct ssh *ssh, u_char pad) { ssh->state->extra_pad = pad; return 0; } diff --git a/crypto/openssh/sshd-session.c b/crypto/openssh/sshd-session.c index 902718524279..62c76cc1c8aa 100644 --- a/crypto/openssh/sshd-session.c +++ b/crypto/openssh/sshd-session.c @@ -1,1431 +1,1438 @@ /* $OpenBSD: sshd-session.c,v 1.12 2025/03/12 22:43:44 djm Exp $ */ /* * SSH2 implementation: * Privilege Separation: * * Copyright (c) 2000, 2001, 2002 Markus Friedl. All rights reserved. * Copyright (c) 2002 Niels Provos. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_SYS_TIME_H # include #endif #include "openbsd-compat/sys-tree.h" #include "openbsd-compat/sys-queue.h" #include #include #include #include #ifdef HAVE_PATHS_H # include #endif #include #include #include #include #include #include #include #include #include #ifdef WITH_OPENSSL #include #include #include #include "openbsd-compat/openssl-compat.h" #endif #ifdef HAVE_SECUREWARE #include #include #endif #include "xmalloc.h" #include "ssh.h" #include "ssh2.h" #include "sshpty.h" #include "packet.h" #include "log.h" #include "sshbuf.h" #include "misc.h" #include "match.h" #include "servconf.h" #include "uidswap.h" #include "compat.h" #include "cipher.h" #include "digest.h" #include "sshkey.h" #include "kex.h" #include "authfile.h" #include "pathnames.h" #include "atomicio.h" #include "canohost.h" #include "hostfile.h" #include "auth.h" #include "authfd.h" #include "msg.h" #include "dispatch.h" #include "channels.h" #include "session.h" #include "monitor.h" #ifdef GSSAPI #include "ssh-gss.h" #endif #include "monitor_wrap.h" #include "auth-options.h" #include "version.h" #include "ssherr.h" #include "sk-api.h" #include "srclimit.h" #include "dh.h" #include "blacklist_client.h" /* Re-exec fds */ #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) #define REEXEC_CONFIG_PASS_FD (STDERR_FILENO + 2) #define REEXEC_MIN_FREE_FD (STDERR_FILENO + 3) /* Privsep fds */ #define PRIVSEP_MONITOR_FD (STDERR_FILENO + 1) #define PRIVSEP_LOG_FD (STDERR_FILENO + 2) #define PRIVSEP_MIN_FREE_FD (STDERR_FILENO + 3) extern char *__progname; /* Server configuration options. */ ServerOptions options; /* Name of the server configuration file. */ char *config_file_name = _PATH_SERVER_CONFIG_FILE; /* * Debug mode flag. This can be set on the command line. If debug * mode is enabled, extra debugging output will be sent to the system * log, the daemon will not go to background, and will exit after processing * the first connection. */ int debug_flag = 0; /* Flag indicating that the daemon is being started from inetd. */ static int inetd_flag = 0; /* debug goes to stderr unless inetd_flag is set */ static int log_stderr = 0; /* Saved arguments to main(). */ static char **saved_argv; static int saved_argc; /* Daemon's agent connection */ int auth_sock = -1; static int have_agent = 0; /* * Any really sensitive data in the application is contained in this * structure. The idea is that this structure could be locked into memory so * that the pages do not get written into swap. However, there are some * problems. The private key contains BIGNUMs, and we do not (in principle) * have access to the internals of them, and locking just the structure is * not very useful. Currently, memory locking is not implemented. */ struct { u_int num_hostkeys; struct sshkey **host_keys; /* all private host keys */ struct sshkey **host_pubkeys; /* all public host keys */ struct sshkey **host_certificates; /* all public host certificates */ } sensitive_data; /* record remote hostname or ip */ u_int utmp_len = HOST_NAME_MAX+1; static int startup_pipe = -1; /* in child */ /* variables used for privilege separation */ struct monitor *pmonitor = NULL; int privsep_is_preauth = 1; static int privsep_chroot = 1; /* Unprivileged user */ struct passwd *privsep_pw = NULL; /* global connection state and authentication contexts */ Authctxt *the_authctxt = NULL; struct ssh *the_active_state; /* global key/cert auth options. XXX move to permanent ssh->authctxt? */ struct sshauthopt *auth_opts = NULL; /* sshd_config buffer */ struct sshbuf *cfg; /* Included files from the configuration file */ struct include_list includes = TAILQ_HEAD_INITIALIZER(includes); /* message to be displayed after login */ struct sshbuf *loginmsg; /* Prototypes for various functions defined later in this file. */ void destroy_sensitive_data(void); void demote_sensitive_data(void); /* XXX reduce to stub once postauth split */ int mm_is_monitor(void) { /* * m_pid is only set in the privileged part, and * points to the unprivileged child. */ return (pmonitor && pmonitor->m_pid > 0); } /* * Signal handler for the alarm after the login grace period has expired. * As usual, this may only take signal-safe actions, even though it is * terminal. */ static void grace_alarm_handler(int sig) { + BLACKLIST_NOTIFY(the_active_state, BLACKLIST_AUTH_FAIL, + "Grace period expired"); /* * Try to kill any processes that we have spawned, E.g. authorized * keys command helpers or privsep children. */ if (getpgid(0) == getpid()) { struct sigaction sa; /* mask all other signals while in handler */ memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_IGN; sigfillset(&sa.sa_mask); #if defined(SA_RESTART) sa.sa_flags = SA_RESTART; #endif (void)sigaction(SIGTERM, &sa, NULL); kill(0, SIGTERM); } _exit(EXIT_LOGIN_GRACE); } /* Destroy the host and server keys. They will no longer be needed. */ void destroy_sensitive_data(void) { u_int i; for (i = 0; i < options.num_host_key_files; i++) { if (sensitive_data.host_keys[i]) { sshkey_free(sensitive_data.host_keys[i]); sensitive_data.host_keys[i] = NULL; } if (sensitive_data.host_certificates[i]) { sshkey_free(sensitive_data.host_certificates[i]); sensitive_data.host_certificates[i] = NULL; } } } /* Demote private to public keys for network child */ void demote_sensitive_data(void) { struct sshkey *tmp; u_int i; int r; for (i = 0; i < options.num_host_key_files; i++) { if (sensitive_data.host_keys[i]) { if ((r = sshkey_from_private( sensitive_data.host_keys[i], &tmp)) != 0) fatal_r(r, "could not demote host %s key", sshkey_type(sensitive_data.host_keys[i])); sshkey_free(sensitive_data.host_keys[i]); sensitive_data.host_keys[i] = tmp; } /* Certs do not need demotion */ } } static void reseed_prngs(void) { u_int32_t rnd[256]; #ifdef WITH_OPENSSL RAND_poll(); #endif arc4random_stir(); /* noop on recent arc4random() implementations */ arc4random_buf(rnd, sizeof(rnd)); /* let arc4random notice PID change */ #ifdef WITH_OPENSSL RAND_seed(rnd, sizeof(rnd)); /* give libcrypto a chance to notice the PID change */ if ((RAND_bytes((u_char *)rnd, 1)) != 1) fatal("%s: RAND_bytes failed", __func__); #endif explicit_bzero(rnd, sizeof(rnd)); } struct sshbuf * pack_hostkeys(void) { struct sshbuf *keybuf = NULL, *hostkeys = NULL; int r; u_int i; if ((hostkeys = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); /* pack hostkeys into a string. Empty key slots get empty strings */ for (i = 0; i < options.num_host_key_files; i++) { /* public key */ if (sensitive_data.host_pubkeys[i] != NULL) { if ((r = sshkey_puts(sensitive_data.host_pubkeys[i], hostkeys)) != 0) fatal_fr(r, "compose hostkey public"); } else { if ((r = sshbuf_put_string(hostkeys, NULL, 0)) != 0) fatal_fr(r, "compose hostkey empty public"); } /* cert */ if (sensitive_data.host_certificates[i] != NULL) { if ((r = sshkey_puts( sensitive_data.host_certificates[i], hostkeys)) != 0) fatal_fr(r, "compose host cert"); } else { if ((r = sshbuf_put_string(hostkeys, NULL, 0)) != 0) fatal_fr(r, "compose host cert empty"); } } sshbuf_free(keybuf); return hostkeys; } static int privsep_preauth(struct ssh *ssh) { int status, r; pid_t pid; /* Set up unprivileged child process to deal with network data */ pmonitor = monitor_init(); /* Store a pointer to the kex for later rekeying */ pmonitor->m_pkex = &ssh->kex; if ((pid = fork()) == -1) fatal("fork of unprivileged child failed"); else if (pid != 0) { debug2("Network child is on pid %ld", (long)pid); pmonitor->m_pid = pid; if (have_agent) { r = ssh_get_authentication_socket(&auth_sock); if (r != 0) { error_r(r, "Could not get agent socket"); have_agent = 0; } } monitor_child_preauth(ssh, pmonitor); /* Wait for the child's exit status */ while (waitpid(pid, &status, 0) == -1) { if (errno == EINTR) continue; pmonitor->m_pid = -1; fatal_f("waitpid: %s", strerror(errno)); } privsep_is_preauth = 0; pmonitor->m_pid = -1; if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) fatal_f("preauth child exited with status %d", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) fatal_f("preauth child terminated by signal %d", WTERMSIG(status)); return 1; } else { /* child */ close(pmonitor->m_sendfd); close(pmonitor->m_log_recvfd); /* * Arrange unpriv-preauth child process fds: * 0, 1 network socket * 2 optional stderr * 3 reserved * 4 monitor message socket * 5 monitor logging socket * * We know that the monitor sockets will have fds > 4 because * of the reserved fds in main() */ if (ssh_packet_get_connection_in(ssh) != STDIN_FILENO && dup2(ssh_packet_get_connection_in(ssh), STDIN_FILENO) == -1) fatal("dup2 stdin failed: %s", strerror(errno)); if (ssh_packet_get_connection_out(ssh) != STDOUT_FILENO && dup2(ssh_packet_get_connection_out(ssh), STDOUT_FILENO) == -1) fatal("dup2 stdout failed: %s", strerror(errno)); /* leave stderr as-is */ log_redirect_stderr_to(NULL); /* dup can clobber log fd */ if (pmonitor->m_recvfd != PRIVSEP_MONITOR_FD && dup2(pmonitor->m_recvfd, PRIVSEP_MONITOR_FD) == -1) fatal("dup2 monitor fd: %s", strerror(errno)); if (pmonitor->m_log_sendfd != PRIVSEP_LOG_FD && dup2(pmonitor->m_log_sendfd, PRIVSEP_LOG_FD) == -1) fatal("dup2 log fd: %s", strerror(errno)); closefrom(PRIVSEP_MIN_FREE_FD); saved_argv[0] = options.sshd_auth_path; execv(options.sshd_auth_path, saved_argv); fatal_f("exec of %s failed: %s", options.sshd_auth_path, strerror(errno)); } } static void privsep_postauth(struct ssh *ssh, Authctxt *authctxt) { int skip_privdrop = 0; /* * Hack for systems that don't support FD passing: retain privileges * in the post-auth privsep process so it can allocate PTYs directly. * This is basically equivalent to what we did <= 9.7, which was to * disable post-auth privsep entriely. * Cygwin doesn't need to drop privs here although it doesn't support * fd passing, as AFAIK PTY allocation on this platform doesn't require * special privileges to begin with. */ #if defined(DISABLE_FD_PASSING) && !defined(HAVE_CYGWIN) skip_privdrop = 1; #endif /* New socket pair */ monitor_reinit(pmonitor); pmonitor->m_pid = fork(); if (pmonitor->m_pid == -1) fatal("fork of unprivileged child failed"); else if (pmonitor->m_pid != 0) { verbose("User child is on pid %ld", (long)pmonitor->m_pid); sshbuf_reset(loginmsg); monitor_clear_keystate(ssh, pmonitor); monitor_child_postauth(ssh, pmonitor); /* NEVERREACHED */ exit(0); } /* child */ close(pmonitor->m_sendfd); pmonitor->m_sendfd = -1; /* Demote the private keys to public keys. */ demote_sensitive_data(); reseed_prngs(); /* Drop privileges */ if (!skip_privdrop) do_setusercontext(authctxt->pw); /* It is safe now to apply the key state */ monitor_apply_keystate(ssh, pmonitor); /* * Tell the packet layer that authentication was successful, since * this information is not part of the key state. */ ssh_packet_set_authenticated(ssh); } static struct sshkey * get_hostkey_by_type(int type, int nid, int need_private, struct ssh *ssh) { u_int i; struct sshkey *key; for (i = 0; i < options.num_host_key_files; i++) { switch (type) { case KEY_RSA_CERT: case KEY_DSA_CERT: case KEY_ECDSA_CERT: case KEY_ED25519_CERT: case KEY_ECDSA_SK_CERT: case KEY_ED25519_SK_CERT: case KEY_XMSS_CERT: key = sensitive_data.host_certificates[i]; break; default: key = sensitive_data.host_keys[i]; if (key == NULL && !need_private) key = sensitive_data.host_pubkeys[i]; break; } if (key == NULL || key->type != type) continue; switch (type) { case KEY_ECDSA: case KEY_ECDSA_SK: case KEY_ECDSA_CERT: case KEY_ECDSA_SK_CERT: if (key->ecdsa_nid != nid) continue; /* FALLTHROUGH */ default: return need_private ? sensitive_data.host_keys[i] : key; } } return NULL; } struct sshkey * get_hostkey_public_by_type(int type, int nid, struct ssh *ssh) { return get_hostkey_by_type(type, nid, 0, ssh); } struct sshkey * get_hostkey_private_by_type(int type, int nid, struct ssh *ssh) { return get_hostkey_by_type(type, nid, 1, ssh); } struct sshkey * get_hostkey_by_index(int ind) { if (ind < 0 || (u_int)ind >= options.num_host_key_files) return (NULL); return (sensitive_data.host_keys[ind]); } struct sshkey * get_hostkey_public_by_index(int ind, struct ssh *ssh) { if (ind < 0 || (u_int)ind >= options.num_host_key_files) return (NULL); return (sensitive_data.host_pubkeys[ind]); } int get_hostkey_index(struct sshkey *key, int compare, struct ssh *ssh) { u_int i; for (i = 0; i < options.num_host_key_files; i++) { if (sshkey_is_cert(key)) { if (key == sensitive_data.host_certificates[i] || (compare && sensitive_data.host_certificates[i] && sshkey_equal(key, sensitive_data.host_certificates[i]))) return (i); } else { if (key == sensitive_data.host_keys[i] || (compare && sensitive_data.host_keys[i] && sshkey_equal(key, sensitive_data.host_keys[i]))) return (i); if (key == sensitive_data.host_pubkeys[i] || (compare && sensitive_data.host_pubkeys[i] && sshkey_equal(key, sensitive_data.host_pubkeys[i]))) return (i); } } return (-1); } /* Inform the client of all hostkeys */ static void notify_hostkeys(struct ssh *ssh) { struct sshbuf *buf; struct sshkey *key; u_int i, nkeys; int r; char *fp; /* Some clients cannot cope with the hostkeys message, skip those. */ if (ssh->compat & SSH_BUG_HOSTKEYS) return; if ((buf = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); for (i = nkeys = 0; i < options.num_host_key_files; i++) { key = get_hostkey_public_by_index(i, ssh); if (key == NULL || key->type == KEY_UNSPEC || sshkey_is_cert(key)) continue; fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_DEFAULT); debug3_f("key %d: %s %s", i, sshkey_ssh_name(key), fp); free(fp); if (nkeys == 0) { /* * Start building the request when we find the * first usable key. */ if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || (r = sshpkt_put_cstring(ssh, "hostkeys-00@openssh.com")) != 0 || (r = sshpkt_put_u8(ssh, 0)) != 0) /* want reply */ sshpkt_fatal(ssh, r, "%s: start request", __func__); } /* Append the key to the request */ sshbuf_reset(buf); if ((r = sshkey_putb(key, buf)) != 0) fatal_fr(r, "couldn't put hostkey %d", i); if ((r = sshpkt_put_stringb(ssh, buf)) != 0) sshpkt_fatal(ssh, r, "%s: append key", __func__); nkeys++; } debug3_f("sent %u hostkeys", nkeys); if (nkeys == 0) fatal_f("no hostkeys"); if ((r = sshpkt_send(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: send", __func__); sshbuf_free(buf); } static void usage(void) { fprintf(stderr, "%s, %s\n", SSH_RELEASE, SSH_OPENSSL_VERSION); fprintf(stderr, "usage: sshd [-46DdeGiqTtV] [-C connection_spec] [-c host_cert_file]\n" " [-E log_file] [-f config_file] [-g login_grace_time]\n" " [-h host_key_file] [-o option] [-p port] [-u len]\n" ); exit(1); } static void parse_hostkeys(struct sshbuf *hostkeys) { int r; u_int num_keys = 0; struct sshkey *k; struct sshbuf *kbuf; const u_char *cp; size_t len; while (sshbuf_len(hostkeys) != 0) { if (num_keys > 2048) fatal_f("too many hostkeys"); sensitive_data.host_keys = xrecallocarray( sensitive_data.host_keys, num_keys, num_keys + 1, sizeof(*sensitive_data.host_pubkeys)); sensitive_data.host_pubkeys = xrecallocarray( sensitive_data.host_pubkeys, num_keys, num_keys + 1, sizeof(*sensitive_data.host_pubkeys)); sensitive_data.host_certificates = xrecallocarray( sensitive_data.host_certificates, num_keys, num_keys + 1, sizeof(*sensitive_data.host_certificates)); /* private key */ k = NULL; if ((r = sshbuf_froms(hostkeys, &kbuf)) != 0) fatal_fr(r, "extract privkey"); if (sshbuf_len(kbuf) != 0 && (r = sshkey_private_deserialize(kbuf, &k)) != 0) fatal_fr(r, "parse pubkey"); sensitive_data.host_keys[num_keys] = k; sshbuf_free(kbuf); if (k) debug2_f("privkey %u: %s", num_keys, sshkey_ssh_name(k)); /* public key */ k = NULL; if ((r = sshbuf_get_string_direct(hostkeys, &cp, &len)) != 0) fatal_fr(r, "extract pubkey"); if (len != 0 && (r = sshkey_from_blob(cp, len, &k)) != 0) fatal_fr(r, "parse pubkey"); sensitive_data.host_pubkeys[num_keys] = k; if (k) debug2_f("pubkey %u: %s", num_keys, sshkey_ssh_name(k)); /* certificate */ k = NULL; if ((r = sshbuf_get_string_direct(hostkeys, &cp, &len)) != 0) fatal_fr(r, "extract pubkey"); if (len != 0 && (r = sshkey_from_blob(cp, len, &k)) != 0) fatal_fr(r, "parse pubkey"); sensitive_data.host_certificates[num_keys] = k; if (k) debug2_f("cert %u: %s", num_keys, sshkey_ssh_name(k)); num_keys++; } sensitive_data.num_hostkeys = num_keys; } static void recv_rexec_state(int fd, struct sshbuf *conf, uint64_t *timing_secretp) { struct sshbuf *m, *inc, *hostkeys; u_char *cp, ver; size_t len; int r; struct include_item *item; debug3_f("entering fd = %d", fd); if ((m = sshbuf_new()) == NULL || (inc = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); /* receive config */ if (ssh_msg_recv(fd, m) == -1) fatal_f("ssh_msg_recv failed"); if ((r = sshbuf_get_u8(m, &ver)) != 0) fatal_fr(r, "parse version"); if (ver != 0) fatal_f("rexec version mismatch"); if ((r = sshbuf_get_string(m, &cp, &len)) != 0 || /* XXX _direct */ (r = sshbuf_get_u64(m, timing_secretp)) != 0 || (r = sshbuf_get_stringb(m, inc)) != 0) fatal_fr(r, "parse config"); if (conf != NULL && (r = sshbuf_put(conf, cp, len))) fatal_fr(r, "sshbuf_put"); while (sshbuf_len(inc) != 0) { item = xcalloc(1, sizeof(*item)); if ((item->contents = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if ((r = sshbuf_get_cstring(inc, &item->selector, NULL)) != 0 || (r = sshbuf_get_cstring(inc, &item->filename, NULL)) != 0 || (r = sshbuf_get_stringb(inc, item->contents)) != 0) fatal_fr(r, "parse includes"); TAILQ_INSERT_TAIL(&includes, item, entry); } /* receive hostkeys */ sshbuf_reset(m); if (ssh_msg_recv(fd, m) == -1) fatal_f("ssh_msg_recv failed"); if ((r = sshbuf_get_u8(m, NULL)) != 0 || (r = sshbuf_froms(m, &hostkeys)) != 0) fatal_fr(r, "parse config"); parse_hostkeys(hostkeys); free(cp); sshbuf_free(m); sshbuf_free(hostkeys); sshbuf_free(inc); debug3_f("done"); } /* * If IP options are supported, make sure there are none (log and * return an error if any are found). Basically we are worried about * source routing; it can be used to pretend you are somebody * (ip-address) you are not. That itself may be "almost acceptable" * under certain circumstances, but rhosts authentication is useless * if source routing is accepted. Notice also that if we just dropped * source routing here, the other side could use IP spoofing to do * rest of the interaction and could still bypass security. So we * exit here if we detect any IP options. */ static void check_ip_options(struct ssh *ssh) { #ifdef IP_OPTIONS int sock_in = ssh_packet_get_connection_in(ssh); struct sockaddr_storage from; u_char opts[200]; socklen_t i, option_size = sizeof(opts), fromlen = sizeof(from); char text[sizeof(opts) * 3 + 1]; memset(&from, 0, sizeof(from)); if (getpeername(sock_in, (struct sockaddr *)&from, &fromlen) == -1) return; if (from.ss_family != AF_INET) return; /* XXX IPv6 options? */ if (getsockopt(sock_in, IPPROTO_IP, IP_OPTIONS, opts, &option_size) >= 0 && option_size != 0) { text[0] = '\0'; for (i = 0; i < option_size; i++) snprintf(text + i*3, sizeof(text) - i*3, " %2.2x", opts[i]); fatal("Connection from %.100s port %d with IP opts: %.800s", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), text); } #endif /* IP_OPTIONS */ } /* Set the routing domain for this process */ static void set_process_rdomain(struct ssh *ssh, const char *name) { #if defined(HAVE_SYS_SET_PROCESS_RDOMAIN) if (name == NULL) return; /* default */ if (strcmp(name, "%D") == 0) { /* "expands" to routing domain of connection */ if ((name = ssh_packet_rdomain_in(ssh)) == NULL) return; } /* NB. We don't pass 'ssh' to sys_set_process_rdomain() */ return sys_set_process_rdomain(name); #elif defined(__OpenBSD__) int rtable, ortable = getrtable(); const char *errstr; if (name == NULL) return; /* default */ if (strcmp(name, "%D") == 0) { /* "expands" to routing domain of connection */ if ((name = ssh_packet_rdomain_in(ssh)) == NULL) return; } rtable = (int)strtonum(name, 0, 255, &errstr); if (errstr != NULL) /* Shouldn't happen */ fatal("Invalid routing domain \"%s\": %s", name, errstr); if (rtable != ortable && setrtable(rtable) != 0) fatal("Unable to set routing domain %d: %s", rtable, strerror(errno)); debug_f("set routing domain %d (was %d)", rtable, ortable); #else /* defined(__OpenBSD__) */ fatal("Unable to set routing domain: not supported in this platform"); #endif } /* * Main program for the daemon. */ int main(int ac, char **av) { struct ssh *ssh = NULL; extern char *optarg; extern int optind; int devnull, r, opt, on = 1, remote_port; int sock_in = -1, sock_out = -1, rexeced_flag = 0, have_key = 0; const char *remote_ip, *rdomain; char *line, *laddr, *logfile = NULL; u_int i; u_int64_t ibytes, obytes; mode_t new_umask; Authctxt *authctxt; struct connection_info *connection_info = NULL; sigset_t sigmask; uint64_t timing_secret = 0; struct itimerval itv; sigemptyset(&sigmask); sigprocmask(SIG_SETMASK, &sigmask, NULL); #ifdef HAVE_SECUREWARE (void)set_auth_parameters(ac, av); #endif __progname = ssh_get_progname(av[0]); /* Save argv. Duplicate so setproctitle emulation doesn't clobber it */ saved_argc = ac; saved_argv = xcalloc(ac + 1, sizeof(*saved_argv)); for (i = 0; (int)i < ac; i++) saved_argv[i] = xstrdup(av[i]); saved_argv[i] = NULL; #ifndef HAVE_SETPROCTITLE /* Prepare for later setproctitle emulation */ compat_init_setproctitle(ac, av); av = saved_argv; #endif /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); /* Initialize configuration options to their default values. */ initialize_server_options(&options); /* Parse command-line arguments. */ while ((opt = getopt(ac, av, "C:E:b:c:f:g:h:k:o:p:u:46DGQRTdeiqrtV")) != -1) { switch (opt) { case '4': options.address_family = AF_INET; break; case '6': options.address_family = AF_INET6; break; case 'f': config_file_name = optarg; break; case 'c': servconf_add_hostcert("[command-line]", 0, &options, optarg); break; case 'd': if (debug_flag == 0) { debug_flag = 1; options.log_level = SYSLOG_LEVEL_DEBUG1; } else if (options.log_level < SYSLOG_LEVEL_DEBUG3) options.log_level++; break; case 'D': /* ignore */ break; case 'E': logfile = optarg; /* FALLTHROUGH */ case 'e': log_stderr = 1; break; case 'i': inetd_flag = 1; break; case 'r': /* ignore */ break; case 'R': rexeced_flag = 1; break; case 'Q': /* ignored */ break; case 'q': options.log_level = SYSLOG_LEVEL_QUIET; break; case 'b': /* protocol 1, ignored */ break; case 'p': options.ports_from_cmdline = 1; if (options.num_ports >= MAX_PORTS) { fprintf(stderr, "too many ports.\n"); exit(1); } options.ports[options.num_ports++] = a2port(optarg); if (options.ports[options.num_ports-1] <= 0) { fprintf(stderr, "Bad port number.\n"); exit(1); } break; case 'g': if ((options.login_grace_time = convtime(optarg)) == -1) { fprintf(stderr, "Invalid login grace time.\n"); exit(1); } break; case 'k': /* protocol 1, ignored */ break; case 'h': servconf_add_hostkey("[command-line]", 0, &options, optarg, 1); break; case 't': case 'T': case 'G': fatal("test/dump modes not supported"); break; case 'C': connection_info = server_get_connection_info(ssh, 0, 0); if (parse_server_match_testspec(connection_info, optarg) == -1) exit(1); break; case 'u': utmp_len = (u_int)strtonum(optarg, 0, HOST_NAME_MAX+1+1, NULL); if (utmp_len > HOST_NAME_MAX+1) { fprintf(stderr, "Invalid utmp length.\n"); exit(1); } break; case 'o': line = xstrdup(optarg); if (process_server_config_line(&options, line, "command-line", 0, NULL, NULL, &includes) != 0) exit(1); free(line); break; case 'V': fprintf(stderr, "%s, %s\n", SSH_RELEASE, SSH_OPENSSL_VERSION); exit(0); default: usage(); break; } } /* Check that there are no remaining arguments. */ if (optind < ac) { fprintf(stderr, "Extra argument %s.\n", av[optind]); exit(1); } if (!rexeced_flag) fatal("sshd-session should not be executed directly"); closefrom(REEXEC_MIN_FREE_FD); platform_pre_session_start(); /* Reserve fds we'll need later for reexec things */ if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) fatal("open %s: %s", _PATH_DEVNULL, strerror(errno)); while (devnull < PRIVSEP_MIN_FREE_FD) { if ((devnull = dup(devnull)) == -1) fatal("dup %s: %s", _PATH_DEVNULL, strerror(errno)); } seed_rng(); /* If requested, redirect the logs to the specified logfile. */ if (logfile != NULL) { char *cp, pid_s[32]; snprintf(pid_s, sizeof(pid_s), "%ld", (unsigned long)getpid()); cp = percent_expand(logfile, "p", pid_s, "P", "sshd-session", (char *)NULL); log_redirect_stderr_to(cp); free(cp); } /* * Force logging to stderr until we have loaded the private host * key (unless started from inetd) */ log_init(__progname, options.log_level == SYSLOG_LEVEL_NOT_SET ? SYSLOG_LEVEL_INFO : options.log_level, options.log_facility == SYSLOG_FACILITY_NOT_SET ? SYSLOG_FACILITY_AUTH : options.log_facility, log_stderr || !inetd_flag || debug_flag); /* Fetch our configuration */ if ((cfg = sshbuf_new()) == NULL) fatal("sshbuf_new config buf failed"); setproctitle("%s", "[rexeced]"); recv_rexec_state(REEXEC_CONFIG_PASS_FD, cfg, &timing_secret); parse_server_config(&options, "rexec", cfg, &includes, NULL, 1); /* Fill in default values for those options not explicitly set. */ fill_default_server_options(&options); options.timing_secret = timing_secret; /* Reinit logging in case config set Level, Facility or Verbose. */ log_init(__progname, options.log_level, options.log_facility, log_stderr || !inetd_flag || debug_flag); debug("sshd-session version %s, %s", SSH_VERSION, SSH_OPENSSL_VERSION); /* Store privilege separation user for later use if required. */ privsep_chroot = (getuid() == 0 || geteuid() == 0); if ((privsep_pw = getpwnam(SSH_PRIVSEP_USER)) == NULL) { if (privsep_chroot || options.kerberos_authentication) fatal("Privilege separation user %s does not exist", SSH_PRIVSEP_USER); } else { privsep_pw = pwcopy(privsep_pw); freezero(privsep_pw->pw_passwd, strlen(privsep_pw->pw_passwd)); privsep_pw->pw_passwd = xstrdup("*"); } endpwent(); if (!debug_flag && !inetd_flag) { if ((startup_pipe = dup(REEXEC_CONFIG_PASS_FD)) == -1) fatal("internal error: no startup pipe"); /* * Signal parent that this child is at a point where * they can go away if they have a SIGHUP pending. */ (void)atomicio(vwrite, startup_pipe, "\0", 1); } /* close the fd, but keep the slot reserved */ if (dup2(devnull, REEXEC_CONFIG_PASS_FD) == -1) fatal("dup2 devnull->config fd: %s", strerror(errno)); /* Check that options are sensible */ if (options.authorized_keys_command_user == NULL && (options.authorized_keys_command != NULL && strcasecmp(options.authorized_keys_command, "none") != 0)) fatal("AuthorizedKeysCommand set without " "AuthorizedKeysCommandUser"); if (options.authorized_principals_command_user == NULL && (options.authorized_principals_command != NULL && strcasecmp(options.authorized_principals_command, "none") != 0)) fatal("AuthorizedPrincipalsCommand set without " "AuthorizedPrincipalsCommandUser"); /* * Check whether there is any path through configured auth methods. * Unfortunately it is not possible to verify this generally before * daemonisation in the presence of Match block, but this catches * and warns for trivial misconfigurations that could break login. */ if (options.num_auth_methods != 0) { for (i = 0; i < options.num_auth_methods; i++) { if (auth2_methods_valid(options.auth_methods[i], 1) == 0) break; } if (i >= options.num_auth_methods) fatal("AuthenticationMethods cannot be satisfied by " "enabled authentication methods"); } #ifdef WITH_OPENSSL if (options.moduli_file != NULL) dh_set_moduli_file(options.moduli_file); #endif if (options.host_key_agent) { if (strcmp(options.host_key_agent, SSH_AUTHSOCKET_ENV_NAME)) setenv(SSH_AUTHSOCKET_ENV_NAME, options.host_key_agent, 1); if ((r = ssh_get_authentication_socket(NULL)) == 0) have_agent = 1; else error_r(r, "Could not connect to agent \"%s\"", options.host_key_agent); } if (options.num_host_key_files != sensitive_data.num_hostkeys) { fatal("internal error: hostkeys confused (config %u recvd %u)", options.num_host_key_files, sensitive_data.num_hostkeys); } for (i = 0; i < options.num_host_key_files; i++) { if (sensitive_data.host_keys[i] != NULL || (have_agent && sensitive_data.host_pubkeys[i] != NULL)) { have_key = 1; break; } } if (!have_key) fatal("internal error: monitor received no hostkeys"); /* Ensure that umask disallows at least group and world write */ new_umask = umask(0077) | 0022; (void) umask(new_umask); /* Initialize the log (it is reinitialized below in case we forked). */ if (debug_flag) log_stderr = 1; log_init(__progname, options.log_level, options.log_facility, log_stderr); for (i = 0; i < options.num_log_verbose; i++) log_verbose_add(options.log_verbose[i]); /* Reinitialize the log (because of the fork above). */ log_init(__progname, options.log_level, options.log_facility, log_stderr); /* * Chdir to the root directory so that the current disk can be * unmounted if desired. */ if (chdir("/") == -1) error("chdir(\"/\"): %s", strerror(errno)); /* ignore SIGPIPE */ ssh_signal(SIGPIPE, SIG_IGN); /* Get a connection, either from inetd or rexec */ if (inetd_flag) { /* * NB. must be different fd numbers for the !socket case, * as packet_connection_is_on_socket() depends on this. */ sock_in = dup(STDIN_FILENO); sock_out = dup(STDOUT_FILENO); } else { /* rexec case; accept()ed socket in ancestor listener */ sock_in = sock_out = dup(STDIN_FILENO); } /* * We intentionally do not close the descriptors 0, 1, and 2 * as our code for setting the descriptors won't work if * ttyfd happens to be one of those. */ if (stdfd_devnull(1, 1, !log_stderr) == -1) error("stdfd_devnull failed"); debug("network sockets: %d, %d", sock_in, sock_out); /* This is the child processing a new connection. */ setproctitle("%s", "[accepted]"); /* Executed child processes don't need these. */ fcntl(sock_out, F_SETFD, FD_CLOEXEC); fcntl(sock_in, F_SETFD, FD_CLOEXEC); /* We will not restart on SIGHUP since it no longer makes sense. */ ssh_signal(SIGALRM, SIG_DFL); ssh_signal(SIGHUP, SIG_DFL); ssh_signal(SIGTERM, SIG_DFL); ssh_signal(SIGQUIT, SIG_DFL); ssh_signal(SIGCHLD, SIG_DFL); ssh_signal(SIGINT, SIG_DFL); + BLACKLIST_INIT(); + /* * Register our connection. This turns encryption off because we do * not have a key. */ if ((ssh = ssh_packet_set_connection(NULL, sock_in, sock_out)) == NULL) fatal("Unable to create connection"); the_active_state = ssh; ssh_packet_set_server(ssh); check_ip_options(ssh); /* Prepare the channels layer */ channel_init_channels(ssh); channel_set_af(ssh, options.address_family); server_process_channel_timeouts(ssh); server_process_permitopen(ssh); /* Set SO_KEEPALIVE if requested. */ if (options.tcp_keep_alive && ssh_packet_connection_is_on_socket(ssh) && setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); if ((remote_port = ssh_remote_port(ssh)) < 0) { debug("ssh_remote_port failed"); cleanup_exit(255); } #ifdef HAVE_LOGIN_CAP /* Also caches remote hostname for sandboxed child. */ auth_get_canonical_hostname(ssh, options.use_dns); #endif /* * The rest of the code depends on the fact that * ssh_remote_ipaddr() caches the remote ip, even if * the socket goes away. */ remote_ip = ssh_remote_ipaddr(ssh); #ifdef SSH_AUDIT_EVENTS audit_connection_from(remote_ip, remote_port); #endif rdomain = ssh_packet_rdomain_in(ssh); /* Log the connection. */ laddr = get_local_ipaddr(sock_in); verbose("Connection from %s port %d on %s port %d%s%s%s", remote_ip, remote_port, laddr, ssh_local_port(ssh), rdomain == NULL ? "" : " rdomain \"", rdomain == NULL ? "" : rdomain, rdomain == NULL ? "" : "\""); free(laddr); /* * We don't want to listen forever unless the other side * successfully authenticates itself. So we set up an alarm which is * cleared after successful authentication. A limit of zero * indicates no limit. Note that we don't set the alarm in debugging * mode; it is just annoying to have the server exit just when you * are about to discover the bug. */ ssh_signal(SIGALRM, grace_alarm_handler); if (!debug_flag && options.login_grace_time > 0) { int ujitter = arc4random_uniform(4 * 1000000); timerclear(&itv.it_interval); itv.it_value.tv_sec = options.login_grace_time; itv.it_value.tv_sec += ujitter / 1000000; itv.it_value.tv_usec = ujitter % 1000000; if (setitimer(ITIMER_REAL, &itv, NULL) == -1) fatal("login grace time setitimer failed"); } if ((r = kex_exchange_identification(ssh, -1, - options.version_addendum)) != 0) + options.version_addendum)) != 0) { + BLACKLIST_NOTIFY(ssh, BLACKLIST_AUTH_FAIL, "Banner exchange"); sshpkt_fatal(ssh, r, "banner exchange"); + } ssh_packet_set_nonblocking(ssh); /* allocate authentication context */ authctxt = xcalloc(1, sizeof(*authctxt)); ssh->authctxt = authctxt; /* XXX global for cleanup, access from other modules */ the_authctxt = authctxt; /* Set default key authentication options */ if ((auth_opts = sshauthopt_new_with_keys_defaults()) == NULL) fatal("allocation failed"); /* prepare buffer to collect messages to display to user after login */ if ((loginmsg = sshbuf_new()) == NULL) fatal("sshbuf_new loginmsg failed"); auth_debug_reset(); - BLACKLIST_INIT(); - if (privsep_preauth(ssh) != 1) fatal("privsep_preauth failed"); /* Now user is authenticated */ /* * Cancel the alarm we set to limit the time taken for * authentication. */ timerclear(&itv.it_interval); timerclear(&itv.it_value); if (setitimer(ITIMER_REAL, &itv, NULL) == -1) fatal("login grace time clear failed"); ssh_signal(SIGALRM, SIG_DFL); authctxt->authenticated = 1; if (startup_pipe != -1) { /* signal listener that authentication completed successfully */ (void)atomicio(vwrite, startup_pipe, "\001", 1); close(startup_pipe); startup_pipe = -1; } if (options.routing_domain != NULL) set_process_rdomain(ssh, options.routing_domain); #ifdef SSH_AUDIT_EVENTS audit_event(ssh, SSH_AUTH_SUCCESS); #endif #ifdef GSSAPI if (options.gss_authentication) { temporarily_use_uid(authctxt->pw); ssh_gssapi_storecreds(); restore_uid(); } #endif #ifdef USE_PAM if (options.use_pam) { do_pam_setcred(); do_pam_session(ssh); } #endif /* * In privilege separation, we fork another child and prepare * file descriptor passing. */ privsep_postauth(ssh, authctxt); /* the monitor process [priv] will not return */ ssh_packet_set_timeout(ssh, options.client_alive_interval, options.client_alive_count_max); /* Try to send all our hostkeys to the client */ notify_hostkeys(ssh); /* Start session. */ do_authenticated(ssh, authctxt); /* The connection has been terminated. */ ssh_packet_get_bytes(ssh, &ibytes, &obytes); verbose("Transferred: sent %llu, received %llu bytes", (unsigned long long)obytes, (unsigned long long)ibytes); verbose("Closing connection to %.500s port %d", remote_ip, remote_port); #ifdef USE_PAM if (options.use_pam) finish_pam(); #endif /* USE_PAM */ #ifdef SSH_AUDIT_EVENTS mm_audit_event(ssh, SSH_CONNECTION_CLOSE); #endif ssh_packet_close(ssh); mm_terminate(); exit(0); } int sshd_hostkey_sign(struct ssh *ssh, struct sshkey *privkey, struct sshkey *pubkey, u_char **signature, size_t *slenp, const u_char *data, size_t dlen, const char *alg) { if (privkey) { if (mm_sshkey_sign(ssh, privkey, signature, slenp, data, dlen, alg, options.sk_provider, NULL, ssh->compat) < 0) fatal_f("privkey sign failed"); } else { if (mm_sshkey_sign(ssh, pubkey, signature, slenp, data, dlen, alg, options.sk_provider, NULL, ssh->compat) < 0) fatal_f("pubkey sign failed"); } return 0; } /* server specific fatal cleanup */ void cleanup_exit(int i) { extern int auth_attempted; /* monitor.c */ if (the_active_state != NULL && the_authctxt != NULL) { do_cleanup(the_active_state, the_authctxt); if (privsep_is_preauth && pmonitor != NULL && pmonitor->m_pid > 1) { debug("Killing privsep child %d", pmonitor->m_pid); if (kill(pmonitor->m_pid, SIGKILL) != 0 && errno != ESRCH) { error_f("kill(%d): %s", pmonitor->m_pid, strerror(errno)); } } } #ifdef SSH_AUDIT_EVENTS /* done after do_cleanup so it can cancel the PAM auth 'thread' */ if (the_active_state != NULL && mm_is_monitor()) audit_event(the_active_state, SSH_CONNECTION_ABANDON); #endif /* Override default fatal exit value when auth was attempted */ - if (i == 255 && auth_attempted) + if (i == 255 && auth_attempted) { + BLACKLIST_NOTIFY(the_active_state, BLACKLIST_AUTH_FAIL, + "Fatal exit"); _exit(EXIT_AUTH_ATTEMPTED); + } _exit(i); }