From 71f11376417c30579eb9fbc67fa82334d840bd3f Mon Sep 17 00:00:00 2001 From: djm Date: Fri, 17 May 2024 00:30:23 +0000 Subject: [PATCH] Start the process of splitting sshd into separate binaries. This step splits sshd into a listener and a session binary. More splits are planned. After this changes, the listener binary will validate the configuration, load the hostkeys, listen on port 22 and manage MaxStartups only. All session handling will be performed by a new sshd-session binary that the listener fork+execs. This reduces the listener process to the minimum necessary and sets us up for future work on the sshd-session binary. feedback/ok markus@ deraadt@ NB. if you're updating via source, please restart sshd after installing, otherwise you run the risk of locking yourself out. --- usr.bin/ssh/Makefile | 5 +- usr.bin/ssh/Makefile.inc | 15 +- usr.bin/ssh/auth-rhosts.c | 3 +- usr.bin/ssh/auth.c | 104 +-- usr.bin/ssh/auth.h | 12 +- usr.bin/ssh/auth2-gss.c | 39 +- usr.bin/ssh/auth2-hostbased.c | 15 +- usr.bin/ssh/auth2-kbdint.c | 7 +- usr.bin/ssh/auth2-none.c | 12 +- usr.bin/ssh/auth2-passwd.c | 9 +- usr.bin/ssh/auth2-pubkey.c | 15 +- usr.bin/ssh/auth2.c | 81 +- usr.bin/ssh/channels.c | 22 +- usr.bin/ssh/kex.c | 260 +----- usr.bin/ssh/kex.h | 9 +- usr.bin/ssh/kexgexs.c | 4 +- usr.bin/ssh/misc.c | 15 +- usr.bin/ssh/misc.h | 10 +- usr.bin/ssh/monitor.c | 49 +- usr.bin/ssh/monitor.h | 6 +- usr.bin/ssh/monitor_wrap.c | 178 +++- usr.bin/ssh/monitor_wrap.h | 13 +- usr.bin/ssh/msg.c | 5 +- usr.bin/ssh/packet.c | 90 +- usr.bin/ssh/packet.h | 3 +- usr.bin/ssh/pathnames.h | 5 +- usr.bin/ssh/servconf.c | 107 +-- usr.bin/ssh/servconf.h | 13 +- usr.bin/ssh/serverloop.c | 44 +- usr.bin/ssh/session.c | 23 +- usr.bin/ssh/ssh_api.c | 17 +- usr.bin/ssh/sshd-session.c | 1354 +++++++++++++++++++++++++++++ usr.bin/ssh/sshd-session/Makefile | 71 ++ usr.bin/ssh/sshd.c | 1227 ++++---------------------- usr.bin/ssh/sshd/Makefile | 18 +- 35 files changed, 2041 insertions(+), 1819 deletions(-) create mode 100644 usr.bin/ssh/sshd-session.c create mode 100644 usr.bin/ssh/sshd-session/Makefile diff --git a/usr.bin/ssh/Makefile b/usr.bin/ssh/Makefile index 7e3add906b3..09f8b0ac800 100644 --- a/usr.bin/ssh/Makefile +++ b/usr.bin/ssh/Makefile @@ -1,8 +1,9 @@ -# $OpenBSD: Makefile,v 1.17 2019/10/31 21:22:01 djm Exp $ +# $OpenBSD: Makefile,v 1.18 2024/05/17 00:30:23 djm Exp $ .include -SUBDIR= ssh sshd ssh-add ssh-keygen ssh-agent scp sftp-server \ +SUBDIR= ssh sshd sshd-session \ + ssh-add ssh-keygen ssh-agent scp sftp-server \ ssh-keysign ssh-keyscan sftp ssh-pkcs11-helper ssh-sk-helper distribution: diff --git a/usr.bin/ssh/Makefile.inc b/usr.bin/ssh/Makefile.inc index 84487e7c942..ae7814a282c 100644 --- a/usr.bin/ssh/Makefile.inc +++ b/usr.bin/ssh/Makefile.inc @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile.inc,v 1.89 2024/01/11 01:45:36 djm Exp $ +# $OpenBSD: Makefile.inc,v 1.90 2024/05/17 00:30:23 djm Exp $ .include @@ -25,7 +25,7 @@ CDIAGFLAGS+= -Wstrict-aliasing=2 CDIAGFLAGS+= -Wold-style-definition .endif -#CDIAGFLAGS+= -Werror +CDIAGFLAGS+= -Werror #CDIAGFLAGS+= -fno-common #DEBUG=-g #INSTALL_STRIP= @@ -111,14 +111,17 @@ SRCS_KEYP+= atomicio.c SRCS_KRL+= bitmap.c SRCS_KRL+= krl.c +SRCS_MAC+= mac.c +SRCS_MAC+= hmac.c +SRCS_MAC+= umac.c +SRCS_MAC+= umac128.c + SRCS_PKT+= canohost.c SRCS_PKT+= dispatch.c -SRCS_PKT+= hmac.c SRCS_PKT+= kex.c -SRCS_PKT+= mac.c +SRCS_PKT+= kex-names.c SRCS_PKT+= packet.c -SRCS_PKT+= umac.c -SRCS_PKT+= umac128.c +SRCS_PKT+= ${SRCS_MAC} SRCS_PROT+= channels.c SRCS_PROT+= monitor_fdpass.c diff --git a/usr.bin/ssh/auth-rhosts.c b/usr.bin/ssh/auth-rhosts.c index 07f75532bfd..3717e933cc9 100644 --- a/usr.bin/ssh/auth-rhosts.c +++ b/usr.bin/ssh/auth-rhosts.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth-rhosts.c,v 1.57 2022/12/09 00:17:40 dtucker Exp $ */ +/* $OpenBSD: auth-rhosts.c,v 1.58 2024/05/17 00:30:23 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -42,7 +42,6 @@ /* import */ extern ServerOptions options; -extern int use_privsep; /* * This function processes an rhosts-style file (.rhosts, .shosts, or diff --git a/usr.bin/ssh/auth.c b/usr.bin/ssh/auth.c index c58798d4aa3..3fec9d71be4 100644 --- a/usr.bin/ssh/auth.c +++ b/usr.bin/ssh/auth.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.c,v 1.160 2023/03/05 05:34:09 dtucker Exp $ */ +/* $OpenBSD: auth.c,v 1.161 2024/05/17 00:30:23 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -67,7 +67,6 @@ /* import */ extern ServerOptions options; extern struct include_list includes; -extern int use_privsep; extern struct sshauthopt *auth_opts; /* Debugging messages */ @@ -246,7 +245,7 @@ auth_log(struct ssh *ssh, int authenticated, int partial, const char *authmsg; char *extra = NULL; - if (use_privsep && !mm_is_monitor() && !authctxt->postponed) + if (!mm_is_monitor() && !authctxt->postponed) return; /* Raise logging level */ @@ -420,14 +419,14 @@ getpwnamallow(struct ssh *ssh, const char *user) struct connection_info *ci; u_int i; - ci = get_connection_info(ssh, 1, options.use_dns); + ci = server_get_connection_info(ssh, 1, options.use_dns); ci->user = user; 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]); - process_permitopen(ssh, &options); + server_process_permitopen(ssh); pw = getpwnam(user); if (pw == NULL) { @@ -562,93 +561,6 @@ fakepw(void) return (&fake); } -/* - * Returns the remote DNS hostname as a string. The returned string must not - * be freed. NB. this will usually trigger a DNS query the first time it is - * called. - * This function does additional checks on the hostname to mitigate some - * attacks on based on conflation of hostnames and IP addresses. - */ - -static char * -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("getpeername failed: %.100s", strerror(errno)); - return xstrdup(ntop); - } - - 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); -} - /* * 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 @@ -662,12 +574,10 @@ auth_get_canonical_hostname(struct ssh *ssh, int use_dns) if (!use_dns) return ssh_remote_ipaddr(ssh); - else if (dnsname != NULL) - return dnsname; - else { - dnsname = remote_hostname(ssh); + if (dnsname != NULL) return dnsname; - } + dnsname = ssh_remote_hostname(ssh); + return dnsname; } /* These functions link key/cert options to the auth framework */ diff --git a/usr.bin/ssh/auth.h b/usr.bin/ssh/auth.h index 54c35533ce3..669f72e73b9 100644 --- a/usr.bin/ssh/auth.h +++ b/usr.bin/ssh/auth.h @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.h,v 1.106 2022/06/15 16:08:25 djm Exp $ */ +/* $OpenBSD: auth.h,v 1.107 2024/05/17 00:30:23 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -95,11 +95,15 @@ struct Authctxt { * the client. */ +struct authmethod_cfg { + const char *name; + const char *synonym; + int *enabled; +}; + struct Authmethod { - char *name; - char *synonym; + struct authmethod_cfg *cfg; int (*userauth)(struct ssh *, const char *); - int *enabled; }; /* diff --git a/usr.bin/ssh/auth2-gss.c b/usr.bin/ssh/auth2-gss.c index 7bab9dd1f6c..fcb29096096 100644 --- a/usr.bin/ssh/auth2-gss.c +++ b/usr.bin/ssh/auth2-gss.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth2-gss.c,v 1.34 2023/03/31 04:22:27 djm Exp $ */ +/* $OpenBSD: auth2-gss.c,v 1.35 2024/05/17 00:30:23 djm Exp $ */ /* * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. @@ -46,6 +46,7 @@ #define SSH_GSSAPI_MAX_MECHS 2048 extern ServerOptions options; +extern struct authmethod_cfg methodcfg_gssapi; static int input_gssapi_token(int type, u_int32_t plen, struct ssh *ssh); static int input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh); @@ -111,7 +112,7 @@ userauth_gssapi(struct ssh *ssh, const char *method) return (0); } - if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, &goid)))) { + if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&ctxt, &goid))) { if (ctxt != NULL) ssh_gssapi_delete_ctx(&ctxt); free(doid); @@ -148,7 +149,7 @@ input_gssapi_token(int type, u_int32_t plen, struct ssh *ssh) size_t len; int r; - if (authctxt == NULL || (authctxt->methoddata == NULL && !use_privsep)) + if (authctxt == NULL) fatal("No authentication or GSSAPI context"); gssctxt = authctxt->methoddata; @@ -158,8 +159,8 @@ input_gssapi_token(int type, u_int32_t plen, struct ssh *ssh) recv_tok.value = p; recv_tok.length = len; - maj_status = PRIVSEP(ssh_gssapi_accept_ctx(gssctxt, &recv_tok, - &send_tok, &flags)); + maj_status = mm_ssh_gssapi_accept_ctx(gssctxt, &recv_tok, + &send_tok, &flags); free(p); @@ -212,7 +213,7 @@ input_gssapi_errtok(int type, u_int32_t plen, struct ssh *ssh) u_char *p; size_t len; - if (authctxt == NULL || (authctxt->methoddata == NULL && !use_privsep)) + if (authctxt == NULL) fatal("No authentication or GSSAPI context"); gssctxt = authctxt->methoddata; @@ -223,8 +224,8 @@ input_gssapi_errtok(int type, u_int32_t plen, struct ssh *ssh) recv_tok.length = len; /* Push the error token into GSSAPI to see what it says */ - maj_status = PRIVSEP(ssh_gssapi_accept_ctx(gssctxt, &recv_tok, - &send_tok, NULL)); + maj_status = mm_ssh_gssapi_accept_ctx(gssctxt, &recv_tok, + &send_tok, NULL); free(recv_tok.value); @@ -251,7 +252,7 @@ input_gssapi_exchange_complete(int type, u_int32_t plen, struct ssh *ssh) int r, authenticated; const char *displayname; - if (authctxt == NULL || (authctxt->methoddata == NULL && !use_privsep)) + if (authctxt == NULL) fatal("No authentication or GSSAPI context"); /* @@ -262,11 +263,7 @@ input_gssapi_exchange_complete(int type, u_int32_t plen, struct ssh *ssh) if ((r = sshpkt_get_end(ssh)) != 0) fatal_fr(r, "parse packet"); - authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user)); - - if ((!use_privsep || mm_is_monitor()) && - (displayname = ssh_gssapi_displayname()) != NULL) - auth2_record_info(authctxt, "%s", displayname); + authenticated = mm_ssh_gssapi_userok(authctxt->user); authctxt->postponed = 0; ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); @@ -289,7 +286,7 @@ input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh) u_char *p; size_t len; - if (authctxt == NULL || (authctxt->methoddata == NULL && !use_privsep)) + if (authctxt == NULL) fatal("No authentication or GSSAPI context"); gssctxt = authctxt->methoddata; @@ -307,18 +304,14 @@ input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh) fatal_f("sshbuf_mutable_ptr failed"); gssbuf.length = sshbuf_len(b); - if (!GSS_ERROR(PRIVSEP(ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic)))) - authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user)); + if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic))) + authenticated = mm_ssh_gssapi_userok(authctxt->user); else logit("GSSAPI MIC check failed"); sshbuf_free(b); free(mic.value); - if ((!use_privsep || mm_is_monitor()) && - (displayname = ssh_gssapi_displayname()) != NULL) - auth2_record_info(authctxt, "%s", displayname); - authctxt->postponed = 0; ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_ERRTOK, NULL); @@ -329,9 +322,7 @@ input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh) } Authmethod method_gssapi = { - "gssapi-with-mic", - NULL, + &methodcfg_gssapi, userauth_gssapi, - &options.gss_authentication }; #endif diff --git a/usr.bin/ssh/auth2-hostbased.c b/usr.bin/ssh/auth2-hostbased.c index 644d7bea89d..e2214175246 100644 --- a/usr.bin/ssh/auth2-hostbased.c +++ b/usr.bin/ssh/auth2-hostbased.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth2-hostbased.c,v 1.52 2023/03/05 05:34:09 dtucker Exp $ */ +/* $OpenBSD: auth2-hostbased.c,v 1.53 2024/05/17 00:30:23 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -53,6 +53,7 @@ /* import */ extern ServerOptions options; +extern struct authmethod_cfg methodcfg_hostbased; static int userauth_hostbased(struct ssh *ssh, const char *method) @@ -144,10 +145,10 @@ userauth_hostbased(struct ssh *ssh, const char *method) /* test for allowed key and correct signature */ authenticated = 0; - if (PRIVSEP(hostbased_key_allowed(ssh, authctxt->pw, cuser, - chost, key)) && - PRIVSEP(sshkey_verify(key, sig, slen, - sshbuf_ptr(b), sshbuf_len(b), pkalg, ssh->compat, NULL)) == 0) + if (mm_hostbased_key_allowed(ssh, authctxt->pw, cuser, + chost, key) && + mm_sshkey_verify(key, sig, slen, + sshbuf_ptr(b), sshbuf_len(b), pkalg, ssh->compat, NULL) == 0) authenticated = 1; auth2_record_key(authctxt, authenticated, key); @@ -251,8 +252,6 @@ hostbased_key_allowed(struct ssh *ssh, struct passwd *pw, } Authmethod method_hostbased = { - "hostbased", - NULL, + &methodcfg_hostbased, userauth_hostbased, - &options.hostbased_authentication }; diff --git a/usr.bin/ssh/auth2-kbdint.c b/usr.bin/ssh/auth2-kbdint.c index 034b6eacf60..cb1cef7d02c 100644 --- a/usr.bin/ssh/auth2-kbdint.c +++ b/usr.bin/ssh/auth2-kbdint.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth2-kbdint.c,v 1.14 2021/12/19 22:12:07 djm Exp $ */ +/* $OpenBSD: auth2-kbdint.c,v 1.15 2024/05/17 00:30:23 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -40,6 +40,7 @@ /* import */ extern ServerOptions options; +extern struct authmethod_cfg methodcfg_kbdint; static int userauth_kbdint(struct ssh *ssh, const char *method) @@ -63,8 +64,6 @@ userauth_kbdint(struct ssh *ssh, const char *method) } Authmethod method_kbdint = { - "keyboard-interactive", - NULL, + &methodcfg_kbdint, userauth_kbdint, - &options.kbd_interactive_authentication }; diff --git a/usr.bin/ssh/auth2-none.c b/usr.bin/ssh/auth2-none.c index d34f654e219..ae4e8a32705 100644 --- a/usr.bin/ssh/auth2-none.c +++ b/usr.bin/ssh/auth2-none.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth2-none.c,v 1.25 2023/03/05 05:34:09 dtucker Exp $ */ +/* $OpenBSD: auth2-none.c,v 1.26 2024/05/17 00:30:23 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -44,9 +44,9 @@ /* import */ extern ServerOptions options; +extern struct authmethod_cfg methodcfg_none; -/* "none" is allowed only one time */ -static int none_enabled = 1; +extern int none_enabled; static int userauth_none(struct ssh *ssh, const char *method) @@ -57,13 +57,11 @@ userauth_none(struct ssh *ssh, const char *method) if ((r = sshpkt_get_end(ssh)) != 0) fatal_fr(r, "parse packet"); if (options.permit_empty_passwd && options.password_authentication) - return (PRIVSEP(auth_password(ssh, ""))); + return mm_auth_password(ssh, ""); return (0); } Authmethod method_none = { - "none", - NULL, + &methodcfg_none, userauth_none, - &none_enabled }; diff --git a/usr.bin/ssh/auth2-passwd.c b/usr.bin/ssh/auth2-passwd.c index b9feb71689a..dffbb4a220f 100644 --- a/usr.bin/ssh/auth2-passwd.c +++ b/usr.bin/ssh/auth2-passwd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth2-passwd.c,v 1.21 2022/05/27 04:29:40 dtucker Exp $ */ +/* $OpenBSD: auth2-passwd.c,v 1.22 2024/05/17 00:30:23 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -45,6 +45,7 @@ /* import */ extern ServerOptions options; +extern struct authmethod_cfg methodcfg_passwd; static int userauth_passwd(struct ssh *ssh, const char *method) @@ -64,15 +65,13 @@ userauth_passwd(struct ssh *ssh, const char *method) if (change) logit("password change not supported"); - else if (PRIVSEP(auth_password(ssh, password)) == 1) + else if (mm_auth_password(ssh, password) == 1) authenticated = 1; freezero(password, len); return authenticated; } Authmethod method_passwd = { - "password", - NULL, + &methodcfg_passwd, userauth_passwd, - &options.password_authentication }; diff --git a/usr.bin/ssh/auth2-pubkey.c b/usr.bin/ssh/auth2-pubkey.c index 1e536b1c639..d459e10417e 100644 --- a/usr.bin/ssh/auth2-pubkey.c +++ b/usr.bin/ssh/auth2-pubkey.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth2-pubkey.c,v 1.119 2023/07/27 22:25:17 djm Exp $ */ +/* $OpenBSD: auth2-pubkey.c,v 1.120 2024/05/17 00:30:23 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -69,6 +69,7 @@ /* import */ extern ServerOptions options; +extern struct authmethod_cfg methodcfg_pubkey; static char * format_key(const struct sshkey *key) @@ -216,11 +217,11 @@ userauth_pubkey(struct ssh *ssh, const char *method) #endif /* test for correct signature */ authenticated = 0; - if (PRIVSEP(user_key_allowed(ssh, pw, key, 1, &authopts)) && - PRIVSEP(sshkey_verify(key, sig, slen, + if (mm_user_key_allowed(ssh, pw, key, 1, &authopts) && + mm_sshkey_verify(key, sig, slen, sshbuf_ptr(b), sshbuf_len(b), (ssh->compat & SSH_BUG_SIGTYPE) == 0 ? pkalg : NULL, - ssh->compat, &sig_details)) == 0) { + ssh->compat, &sig_details) == 0) { authenticated = 1; } if (authenticated == 1 && sig_details != NULL) { @@ -278,7 +279,7 @@ userauth_pubkey(struct ssh *ssh, const char *method) * if a user is not allowed to login. is this an * issue? -markus */ - if (PRIVSEP(user_key_allowed(ssh, pw, key, 0, NULL))) { + if (mm_user_key_allowed(ssh, pw, key, 0, NULL)) { if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_PK_OK)) != 0 || (r = sshpkt_put_cstring(ssh, pkalg)) != 0 || @@ -810,8 +811,6 @@ user_key_allowed(struct ssh *ssh, struct passwd *pw, struct sshkey *key, } Authmethod method_pubkey = { - "publickey", - "publickey-hostbound-v00@openssh.com", + &methodcfg_pubkey, userauth_pubkey, - &options.pubkey_authentication }; diff --git a/usr.bin/ssh/auth2.c b/usr.bin/ssh/auth2.c index 40bd28a4702..86ee7874287 100644 --- a/usr.bin/ssh/auth2.c +++ b/usr.bin/ssh/auth2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth2.c,v 1.168 2023/12/18 14:45:49 djm Exp $ */ +/* $OpenBSD: auth2.c,v 1.169 2024/05/17 00:30:23 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -141,7 +141,7 @@ userauth_banner(struct ssh *ssh) if (options.banner == NULL) return; - if ((banner = PRIVSEP(auth2_read_banner())) == NULL) + if ((banner = mm_auth2_read_banner()) == NULL) goto done; if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_BANNER)) != 0 || @@ -281,7 +281,7 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh) auth_maxtries_exceeded(ssh); if (authctxt->attempt++ == 0) { /* setup auth context */ - authctxt->pw = PRIVSEP(getpwnamallow(ssh, user)); + authctxt->pw = mm_getpwnamallow(ssh, user); if (authctxt->pw && strcmp(service, "ssh-connection")==0) { authctxt->valid = 1; debug2_f("setting up authctxt for %s", user); @@ -292,13 +292,11 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh) } ssh_packet_set_log_preamble(ssh, "%suser %s", authctxt->valid ? "authenticating " : "invalid ", user); - setproctitle("%s%s", authctxt->valid ? user : "unknown", - use_privsep ? " [net]" : ""); + setproctitle("%s [net]", authctxt->valid ? user : "unknown"); authctxt->user = xstrdup(user); authctxt->service = xstrdup(service); authctxt->style = style ? xstrdup(style) : NULL; - if (use_privsep) - mm_inform_authserv(service, style); + 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"); @@ -362,7 +360,7 @@ userauth_finish(struct ssh *ssh, int authenticated, const char *packet_method, /* prefer primary authmethod name to possible synonym */ if ((m = authmethod_byname(method)) == NULL) fatal("INTERNAL ERROR: bad method %s", method); - method = m->name; + method = m->cfg->name; } /* Special handling for root */ @@ -453,16 +451,16 @@ authmethods_get(Authctxt *authctxt) if ((b = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); for (i = 0; authmethods[i] != NULL; i++) { - if (strcmp(authmethods[i]->name, "none") == 0) + if (strcmp(authmethods[i]->cfg->name, "none") == 0) continue; - if (authmethods[i]->enabled == NULL || - *(authmethods[i]->enabled) == 0) + if (authmethods[i]->cfg->enabled == NULL || + *(authmethods[i]->cfg->enabled) == 0) continue; - if (!auth2_method_allowed(authctxt, authmethods[i]->name, + if (!auth2_method_allowed(authctxt, authmethods[i]->cfg->name, NULL)) continue; if ((r = sshbuf_putf(b, "%s%s", sshbuf_len(b) ? "," : "", - authmethods[i]->name)) != 0) + authmethods[i]->cfg->name)) != 0) fatal_fr(r, "buffer error"); } if ((list = sshbuf_dup_string(b)) == NULL) @@ -479,9 +477,9 @@ authmethod_byname(const char *name) if (name == NULL) fatal_f("NULL authentication method name"); for (i = 0; authmethods[i] != NULL; i++) { - if (strcmp(name, authmethods[i]->name) == 0 || - (authmethods[i]->synonym != NULL && - strcmp(name, authmethods[i]->synonym) == 0)) + 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); @@ -496,11 +494,11 @@ authmethod_lookup(Authctxt *authctxt, const char *name) if ((method = authmethod_byname(name)) == NULL) return NULL; - if (method->enabled == NULL || *(method->enabled) == 0) { + if (method->cfg->enabled == NULL || *(method->cfg->enabled) == 0) { debug3_f("method %s not enabled", name); return NULL; } - if (!auth2_method_allowed(authctxt, method->name, NULL)) { + if (!auth2_method_allowed(authctxt, method->cfg->name, NULL)) { debug3_f("method %s not allowed " "by AuthenticationMethods", name); return NULL; @@ -508,53 +506,6 @@ authmethod_lookup(Authctxt *authctxt, const char *name) return method; } -/* - * Check a comma-separated list of methods for validity. Is need_enable is - * non-zero, then also require that the methods are enabled. - * Returns 0 on success or -1 if the methods list is invalid. - */ -int -auth2_methods_valid(const char *_methods, int need_enable) -{ - char *methods, *omethods, *method, *p; - u_int i, found; - int ret = -1; - - if (*_methods == '\0') { - error("empty authentication method list"); - return -1; - } - omethods = methods = xstrdup(_methods); - while ((method = strsep(&methods, ",")) != NULL) { - for (found = i = 0; !found && authmethods[i] != NULL; i++) { - if ((p = strchr(method, ':')) != NULL) - *p = '\0'; - if (strcmp(method, authmethods[i]->name) != 0) - continue; - if (need_enable) { - if (authmethods[i]->enabled == NULL || - *(authmethods[i]->enabled) == 0) { - error("Disabled method \"%s\" in " - "AuthenticationMethods list \"%s\"", - method, _methods); - goto out; - } - } - found = 1; - break; - } - if (!found) { - error("Unknown authentication method \"%s\" in list", - method); - goto out; - } - } - ret = 0; - out: - free(omethods); - return ret; -} - /* * Prune the AuthenticationMethods supplied in the configuration, removing * any methods lists that include disabled methods. Note that this might diff --git a/usr.bin/ssh/channels.c b/usr.bin/ssh/channels.c index d07232df21b..281cecf246c 100644 --- a/usr.bin/ssh/channels.c +++ b/usr.bin/ssh/channels.c @@ -1,4 +1,4 @@ -/* $OpenBSD: channels.c,v 1.437 2024/03/06 02:59:59 djm Exp $ */ +/* $OpenBSD: channels.c,v 1.438 2024/05/17 00:30:23 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -85,13 +85,6 @@ /* -- agent forwarding */ #define NUM_SOCKS 10 -/* -- tcp forwarding */ -/* special-case port number meaning allow any port */ -#define FWD_PERMIT_ANY_PORT 0 - -/* special-case wildcard meaning allow any host */ -#define FWD_PERMIT_ANY_HOST "*" - /* -- X11 forwarding */ /* Maximum number of fake X11 displays to try. */ #define MAX_DISPLAYS 1000 @@ -4530,19 +4523,6 @@ channel_update_permission(struct ssh *ssh, int idx, int newport) } } -/* returns port number, FWD_PERMIT_ANY_PORT or -1 on error */ -int -permitopen_port(const char *p) -{ - int port; - - if (strcmp(p, "*") == 0) - return FWD_PERMIT_ANY_PORT; - if ((port = a2port(p)) > 0) - return port; - return -1; -} - /* Try to start non-blocking connect to next host in cctx list */ static int connect_next(struct channel_connect *cctx) diff --git a/usr.bin/ssh/kex.c b/usr.bin/ssh/kex.c index 1e9ad51d882..a99d050bbb3 100644 --- a/usr.bin/ssh/kex.c +++ b/usr.bin/ssh/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.185 2024/01/08 00:34:33 djm Exp $ */ +/* $OpenBSD: kex.c,v 1.186 2024/05/17 00:30:23 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -76,244 +76,6 @@ static const char * const proposal_names[PROPOSAL_MAX] = { "languages stoc", }; -struct kexalg { - char *name; - u_int type; - int ec_nid; - int hash_alg; -}; -static const struct kexalg kexalgs[] = { -#ifdef WITH_OPENSSL - { KEX_DH1, KEX_DH_GRP1_SHA1, 0, SSH_DIGEST_SHA1 }, - { KEX_DH14_SHA1, KEX_DH_GRP14_SHA1, 0, SSH_DIGEST_SHA1 }, - { KEX_DH14_SHA256, KEX_DH_GRP14_SHA256, 0, SSH_DIGEST_SHA256 }, - { KEX_DH16_SHA512, KEX_DH_GRP16_SHA512, 0, SSH_DIGEST_SHA512 }, - { KEX_DH18_SHA512, KEX_DH_GRP18_SHA512, 0, SSH_DIGEST_SHA512 }, - { KEX_DHGEX_SHA1, KEX_DH_GEX_SHA1, 0, SSH_DIGEST_SHA1 }, - { KEX_DHGEX_SHA256, KEX_DH_GEX_SHA256, 0, SSH_DIGEST_SHA256 }, - { KEX_ECDH_SHA2_NISTP256, KEX_ECDH_SHA2, - NID_X9_62_prime256v1, SSH_DIGEST_SHA256 }, - { KEX_ECDH_SHA2_NISTP384, KEX_ECDH_SHA2, NID_secp384r1, - SSH_DIGEST_SHA384 }, - { KEX_ECDH_SHA2_NISTP521, KEX_ECDH_SHA2, NID_secp521r1, - SSH_DIGEST_SHA512 }, -#endif - { KEX_CURVE25519_SHA256, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, - { KEX_CURVE25519_SHA256_OLD, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, - { KEX_SNTRUP761X25519_SHA512, KEX_KEM_SNTRUP761X25519_SHA512, 0, - SSH_DIGEST_SHA512 }, - { NULL, 0, -1, -1}, -}; - -char * -kex_alg_list(char sep) -{ - char *ret = NULL, *tmp; - size_t nlen, rlen = 0; - const struct kexalg *k; - - for (k = kexalgs; k->name != NULL; k++) { - if (ret != NULL) - ret[rlen++] = sep; - nlen = strlen(k->name); - if ((tmp = realloc(ret, rlen + nlen + 2)) == NULL) { - free(ret); - return NULL; - } - ret = tmp; - memcpy(ret + rlen, k->name, nlen + 1); - rlen += nlen; - } - return ret; -} - -static const struct kexalg * -kex_alg_by_name(const char *name) -{ - const struct kexalg *k; - - for (k = kexalgs; k->name != NULL; k++) { - if (strcmp(k->name, name) == 0) - return k; - } - return NULL; -} - -/* Validate KEX method name list */ -int -kex_names_valid(const char *names) -{ - char *s, *cp, *p; - - if (names == NULL || strcmp(names, "") == 0) - return 0; - if ((s = cp = strdup(names)) == NULL) - return 0; - for ((p = strsep(&cp, ",")); p && *p != '\0'; - (p = strsep(&cp, ","))) { - if (kex_alg_by_name(p) == NULL) { - error("Unsupported KEX algorithm \"%.100s\"", p); - free(s); - return 0; - } - } - debug3("kex names ok: [%s]", names); - free(s); - return 1; -} - -/* returns non-zero if proposal contains any algorithm from algs */ -static int -has_any_alg(const char *proposal, const char *algs) -{ - char *cp; - - if ((cp = match_list(proposal, algs, NULL)) == NULL) - return 0; - free(cp); - return 1; -} - -/* - * Concatenate algorithm names, avoiding duplicates in the process. - * Caller must free returned string. - */ -char * -kex_names_cat(const char *a, const char *b) -{ - char *ret = NULL, *tmp = NULL, *cp, *p; - size_t len; - - if (a == NULL || *a == '\0') - return strdup(b); - if (b == NULL || *b == '\0') - return strdup(a); - if (strlen(b) > 1024*1024) - return NULL; - len = strlen(a) + strlen(b) + 2; - if ((tmp = cp = strdup(b)) == NULL || - (ret = calloc(1, len)) == NULL) { - free(tmp); - return NULL; - } - strlcpy(ret, a, len); - for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) { - if (has_any_alg(ret, p)) - continue; /* Algorithm already present */ - if (strlcat(ret, ",", len) >= len || - strlcat(ret, p, len) >= len) { - free(tmp); - free(ret); - return NULL; /* Shouldn't happen */ - } - } - free(tmp); - return ret; -} - -/* - * Assemble a list of algorithms from a default list and a string from a - * configuration file. The user-provided string may begin with '+' to - * indicate that it should be appended to the default, '-' that the - * specified names should be removed, or '^' that they should be placed - * at the head. - */ -int -kex_assemble_names(char **listp, const char *def, const char *all) -{ - char *cp, *tmp, *patterns; - char *list = NULL, *ret = NULL, *matching = NULL, *opatterns = NULL; - int r = SSH_ERR_INTERNAL_ERROR; - - if (listp == NULL || def == NULL || all == NULL) - return SSH_ERR_INVALID_ARGUMENT; - - if (*listp == NULL || **listp == '\0') { - if ((*listp = strdup(def)) == NULL) - return SSH_ERR_ALLOC_FAIL; - return 0; - } - - list = *listp; - *listp = NULL; - if (*list == '+') { - /* Append names to default list */ - if ((tmp = kex_names_cat(def, list + 1)) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto fail; - } - free(list); - list = tmp; - } else if (*list == '-') { - /* Remove names from default list */ - if ((*listp = match_filter_denylist(def, list + 1)) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto fail; - } - free(list); - /* filtering has already been done */ - return 0; - } else if (*list == '^') { - /* Place names at head of default list */ - if ((tmp = kex_names_cat(list + 1, def)) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto fail; - } - free(list); - list = tmp; - } else { - /* Explicit list, overrides default - just use "list" as is */ - } - - /* - * The supplied names may be a pattern-list. For the -list case, - * the patterns are applied above. For the +list and explicit list - * cases we need to do it now. - */ - ret = NULL; - if ((patterns = opatterns = strdup(list)) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto fail; - } - /* Apply positive (i.e. non-negated) patterns from the list */ - while ((cp = strsep(&patterns, ",")) != NULL) { - if (*cp == '!') { - /* negated matches are not supported here */ - r = SSH_ERR_INVALID_ARGUMENT; - goto fail; - } - free(matching); - if ((matching = match_filter_allowlist(all, cp)) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto fail; - } - if ((tmp = kex_names_cat(ret, matching)) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto fail; - } - free(ret); - ret = tmp; - } - if (ret == NULL || *ret == '\0') { - /* An empty name-list is an error */ - /* XXX better error code? */ - r = SSH_ERR_INVALID_ARGUMENT; - goto fail; - } - - /* success */ - *listp = ret; - ret = NULL; - r = 0; - - fail: - free(matching); - free(opatterns); - free(list); - free(ret); - return r; -} - /* * Fill out a proposal array with dynamically allocated values, which may * be modified as required for compatibility reasons. @@ -512,11 +274,11 @@ kex_set_server_sig_algs(struct ssh *ssh, const char *allowed_algs) (alg = strsep(&algs, ","))) { if ((sigalg = sshkey_sigalg_by_name(alg)) == NULL) continue; - if (!has_any_alg(sigalg, sigalgs)) + if (!kex_has_any_alg(sigalg, sigalgs)) continue; /* Don't add an algorithm twice. */ if (ssh->kex->server_sig_algs != NULL && - has_any_alg(sigalg, ssh->kex->server_sig_algs)) + kex_has_any_alg(sigalg, ssh->kex->server_sig_algs)) continue; xextendf(&ssh->kex->server_sig_algs, ",", "%s", sigalg); } @@ -1091,20 +853,18 @@ choose_comp(struct sshcomp *comp, char *client, char *server) static int choose_kex(struct kex *k, char *client, char *server) { - const struct kexalg *kexalg; - k->name = match_list(client, server, NULL); debug("kex: algorithm: %s", k->name ? k->name : "(no match)"); if (k->name == NULL) return SSH_ERR_NO_KEX_ALG_MATCH; - if ((kexalg = kex_alg_by_name(k->name)) == NULL) { + if (!kex_name_valid(k->name)) { error_f("unsupported KEX method %s", k->name); return SSH_ERR_INTERNAL_ERROR; } - k->kex_type = kexalg->type; - k->hash_alg = kexalg->hash_alg; - k->ec_nid = kexalg->ec_nid; + k->kex_type = kex_type_from_name(k->name); + k->hash_alg = kex_hash_from_name(k->name); + k->ec_nid = kex_nid_from_name(k->name); return 0; } @@ -1154,7 +914,7 @@ proposals_match(char *my[PROPOSAL_MAX], char *peer[PROPOSAL_MAX]) static int kexalgs_contains(char **peer, const char *ext) { - return has_any_alg(peer[PROPOSAL_KEX_ALGS], ext); + return kex_has_any_alg(peer[PROPOSAL_KEX_ALGS], ext); } static int @@ -1205,10 +965,10 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq) /* Check whether client supports rsa-sha2 algorithms */ if (kex->server && (kex->flags & KEX_INITIAL)) { - if (has_any_alg(peer[PROPOSAL_SERVER_HOST_KEY_ALGS], + if (kex_has_any_alg(peer[PROPOSAL_SERVER_HOST_KEY_ALGS], "rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com")) kex->flags |= KEX_RSA_SHA2_256_SUPPORTED; - if (has_any_alg(peer[PROPOSAL_SERVER_HOST_KEY_ALGS], + if (kex_has_any_alg(peer[PROPOSAL_SERVER_HOST_KEY_ALGS], "rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com")) kex->flags |= KEX_RSA_SHA2_512_SUPPORTED; } diff --git a/usr.bin/ssh/kex.h b/usr.bin/ssh/kex.h index 59aebf3f1a8..ca8d5aa4744 100644 --- a/usr.bin/ssh/kex.h +++ b/usr.bin/ssh/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.122 2024/02/02 00:13:34 djm Exp $ */ +/* $OpenBSD: kex.h,v 1.123 2024/05/17 00:30:23 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -85,7 +85,7 @@ enum kex_modes { }; enum kex_exchange { - KEX_DH_GRP1_SHA1, + KEX_DH_GRP1_SHA1 = 1, KEX_DH_GRP14_SHA1, KEX_DH_GRP14_SHA256, KEX_DH_GRP16_SHA512, @@ -176,9 +176,14 @@ struct kex { struct sshbuf *client_pub; }; +int kex_name_valid(const char *); +u_int kex_type_from_name(const char *); +int kex_hash_from_name(const char *); +int kex_nid_from_name(const char *); int kex_names_valid(const char *); char *kex_alg_list(char); char *kex_names_cat(const char *, const char *); +int kex_has_any_alg(const char *, const char *); int kex_assemble_names(char **, const char *, const char *); void kex_proposal_populate_entries(struct ssh *, char *prop[PROPOSAL_MAX], const char *, const char *, const char *, const char *, const char *); diff --git a/usr.bin/ssh/kexgexs.c b/usr.bin/ssh/kexgexs.c index ea83bb27241..1f7e2641ce7 100644 --- a/usr.bin/ssh/kexgexs.c +++ b/usr.bin/ssh/kexgexs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexgexs.c,v 1.46 2023/03/29 01:07:48 dtucker Exp $ */ +/* $OpenBSD: kexgexs.c,v 1.47 2024/05/17 00:30:23 djm Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -91,7 +91,7 @@ input_kex_dh_gex_request(int type, u_int32_t seq, struct ssh *ssh) } /* Contact privileged parent */ - kex->dh = PRIVSEP(choose_dh(min, nbits, max)); + kex->dh = mm_choose_dh(min, nbits, max); if (kex->dh == NULL) { (void)sshpkt_disconnect(ssh, "no matching DH grp found"); r = SSH_ERR_ALLOC_FAIL; diff --git a/usr.bin/ssh/misc.c b/usr.bin/ssh/misc.c index 36b1cbb478f..59127b6e24d 100644 --- a/usr.bin/ssh/misc.c +++ b/usr.bin/ssh/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.193 2024/04/02 10:02:08 deraadt Exp $ */ +/* $OpenBSD: misc.c,v 1.194 2024/05/17 00:30:23 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005-2020 Damien Miller. All rights reserved. @@ -1909,6 +1909,19 @@ forward_equals(const struct Forward *a, const struct Forward *b) return 1; } +/* returns port number, FWD_PERMIT_ANY_PORT or -1 on error */ +int +permitopen_port(const char *p) +{ + int port; + + if (strcmp(p, "*") == 0) + return FWD_PERMIT_ANY_PORT; + if ((port = a2port(p)) > 0) + return port; + return -1; +} + /* returns 1 if process is already daemonized, 0 otherwise */ int daemonized(void) diff --git a/usr.bin/ssh/misc.h b/usr.bin/ssh/misc.h index d460dc7cd62..00b8bc5d06c 100644 --- a/usr.bin/ssh/misc.h +++ b/usr.bin/ssh/misc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.h,v 1.107 2024/03/04 02:16:11 djm Exp $ */ +/* $OpenBSD: misc.h,v 1.108 2024/05/17 00:30:24 djm Exp $ */ /* * Author: Tatu Ylonen @@ -21,6 +21,12 @@ #include #include +/* special-case port number meaning allow any port */ +#define FWD_PERMIT_ANY_PORT 0 + +/* special-case wildcard meaning allow any host */ +#define FWD_PERMIT_ANY_HOST "*" + /* Data structure for representing a forwarding request. */ struct Forward { char *listen_host; /* Host (address) to listen on. */ @@ -34,6 +40,8 @@ struct Forward { }; int forward_equals(const struct Forward *, const struct Forward *); +int permitopen_port(const char *p); + int daemonized(void); /* Common server and client forwarding options. */ diff --git a/usr.bin/ssh/monitor.c b/usr.bin/ssh/monitor.c index 8746a8e2cf8..6c8fc6f39de 100644 --- a/usr.bin/ssh/monitor.c +++ b/usr.bin/ssh/monitor.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor.c,v 1.237 2023/08/16 16:14:11 djm Exp $ */ +/* $OpenBSD: monitor.c,v 1.238 2024/05/17 00:30:24 djm Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -641,13 +641,39 @@ mm_answer_sign(struct ssh *ssh, int sock, struct sshbuf *m) 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; - u_int i; debug3_f("entering"); @@ -692,24 +718,9 @@ mm_answer_pwnamallow(struct ssh *ssh, int sock, struct sshbuf *m) out: ssh_packet_set_log_preamble(ssh, "%suser %s", authctxt->valid ? "authenticating" : "invalid ", authctxt->user); - 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 + /* Send active options to unpriv */ + mm_encode_server_options(m); /* Create valid auth method lists */ if (auth2_setup_methods_lists(authctxt) != 0) { diff --git a/usr.bin/ssh/monitor.h b/usr.bin/ssh/monitor.h index 15402c409af..7049e05db13 100644 --- a/usr.bin/ssh/monitor.h +++ b/usr.bin/ssh/monitor.h @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor.h,v 1.23 2019/01/19 21:43:56 djm Exp $ */ +/* $OpenBSD: monitor.h,v 1.24 2024/05/17 00:30:24 djm Exp $ */ /* * Copyright 2002 Niels Provos @@ -59,6 +59,7 @@ enum monitor_reqtype { }; struct ssh; +struct sshbuf; struct monitor { int m_recvfd; @@ -85,4 +86,7 @@ void mm_request_receive(int, struct sshbuf *); void mm_request_receive_expect(int, enum monitor_reqtype, struct sshbuf *); void mm_get_keystate(struct ssh *, struct monitor *); +/* XXX: should be returned via a monitor call rather than config_fd */ +void mm_encode_server_options(struct sshbuf *); + #endif /* _MONITOR_H_ */ diff --git a/usr.bin/ssh/monitor_wrap.c b/usr.bin/ssh/monitor_wrap.c index 3115dfee9db..6287a8c7110 100644 --- a/usr.bin/ssh/monitor_wrap.c +++ b/usr.bin/ssh/monitor_wrap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor_wrap.c,v 1.129 2023/12/18 14:45:49 djm Exp $ */ +/* $OpenBSD: monitor_wrap.c,v 1.130 2024/05/17 00:30:24 djm Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -61,7 +61,6 @@ #ifdef GSSAPI #include "ssh-gss.h" #endif -#include "monitor_wrap.h" #include "atomicio.h" #include "monitor_fdpass.h" #include "misc.h" @@ -69,6 +68,7 @@ #include "channels.h" #include "session.h" #include "servconf.h" +#include "monitor_wrap.h" #include "ssherr.h" @@ -143,8 +143,10 @@ mm_request_receive(int sock, struct sshbuf *m) debug3_f("entering"); if (atomicio(read, sock, buf, sizeof(buf)) != sizeof(buf)) { - if (errno == EPIPE) + if (errno == EPIPE) { + debug3_f("monitor fd closed"); cleanup_exit(255); + } fatal_f("read: %s", strerror(errno)); } msg_len = PEEK_U32(buf); @@ -239,6 +241,49 @@ mm_sshkey_sign(struct ssh *ssh, struct sshkey *key, u_char **sigp, size_t *lenp, return (0); } +void +mm_decode_activate_server_options(struct ssh *ssh, struct sshbuf *m) +{ + const u_char *p; + size_t len; + u_int i; + ServerOptions *newopts; + int r; + + if ((r = sshbuf_get_string_direct(m, &p, &len)) != 0) + fatal_fr(r, "parse opts"); + if (len != sizeof(*newopts)) + fatal_f("option block size mismatch"); + newopts = xcalloc(sizeof(*newopts), 1); + memcpy(newopts, p, sizeof(*newopts)); + +#define M_CP_STROPT(x) do { \ + if (newopts->x != NULL && \ + (r = sshbuf_get_cstring(m, &newopts->x, NULL)) != 0) \ + fatal_fr(r, "parse %s", #x); \ + } while (0) +#define M_CP_STRARRAYOPT(x, nx) do { \ + newopts->x = newopts->nx == 0 ? \ + NULL : xcalloc(newopts->nx, sizeof(*newopts->x)); \ + for (i = 0; i < newopts->nx; i++) { \ + if ((r = sshbuf_get_cstring(m, \ + &newopts->x[i], NULL)) != 0) \ + fatal_fr(r, "parse %s", #x); \ + } \ + } while (0) + /* See comment in servconf.h */ + COPY_MATCH_STRING_OPTS(); +#undef M_CP_STROPT +#undef M_CP_STRARRAYOPT + + copy_set_server_options(&options, newopts, 1); + 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]); + free(newopts); +} + #define GETPW(b, id) \ do { \ if ((r = sshbuf_get_string_direct(b, &p, &len)) != 0) \ @@ -254,8 +299,6 @@ mm_getpwnamallow(struct ssh *ssh, const char *username) struct sshbuf *m; struct passwd *pw; size_t len; - u_int i; - ServerOptions *newopts; int r; u_char ok; const u_char *p; @@ -294,41 +337,10 @@ mm_getpwnamallow(struct ssh *ssh, const char *username) out: /* copy options block as a Match directive may have changed some */ - if ((r = sshbuf_get_string_direct(m, &p, &len)) != 0) - fatal_fr(r, "parse opts"); - if (len != sizeof(*newopts)) - fatal_f("option block size mismatch"); - newopts = xcalloc(sizeof(*newopts), 1); - memcpy(newopts, p, sizeof(*newopts)); - -#define M_CP_STROPT(x) do { \ - if (newopts->x != NULL && \ - (r = sshbuf_get_cstring(m, &newopts->x, NULL)) != 0) \ - fatal_fr(r, "parse %s", #x); \ - } while (0) -#define M_CP_STRARRAYOPT(x, nx) do { \ - newopts->x = newopts->nx == 0 ? \ - NULL : xcalloc(newopts->nx, sizeof(*newopts->x)); \ - for (i = 0; i < newopts->nx; i++) { \ - if ((r = sshbuf_get_cstring(m, \ - &newopts->x[i], NULL)) != 0) \ - fatal_fr(r, "parse %s", #x); \ - } \ - } while (0) - /* See comment in servconf.h */ - COPY_MATCH_STRING_OPTS(); -#undef M_CP_STROPT -#undef M_CP_STRARRAYOPT - - copy_set_server_options(&options, newopts, 1); - 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]); - process_permitopen(ssh, &options); - process_channel_timeouts(ssh, &options); + mm_decode_activate_server_options(ssh, m); + server_process_permitopen(ssh); + server_process_channel_timeouts(ssh); kex_set_server_sig_algs(ssh, options.pubkey_accepted_algos); - free(newopts); sshbuf_free(m); return (pw); @@ -807,3 +819,91 @@ mm_ssh_gssapi_userok(char *user) return (authenticated); } #endif /* GSSAPI */ + +/* + * Inform channels layer of permitopen options for a single forwarding + * direction (local/remote). + */ +static void +server_process_permitopen_list(struct ssh *ssh, int listen, + char **opens, u_int num_opens) +{ + u_int i; + int port; + char *host, *arg, *oarg; + int where = listen ? FORWARD_REMOTE : FORWARD_LOCAL; + const char *what = listen ? "permitlisten" : "permitopen"; + + channel_clear_permission(ssh, FORWARD_ADM, where); + if (num_opens == 0) + return; /* permit any */ + + /* handle keywords: "any" / "none" */ + if (num_opens == 1 && strcmp(opens[0], "any") == 0) + return; + if (num_opens == 1 && strcmp(opens[0], "none") == 0) { + channel_disable_admin(ssh, where); + return; + } + /* Otherwise treat it as a list of permitted host:port */ + for (i = 0; i < num_opens; i++) { + oarg = arg = xstrdup(opens[i]); + host = hpdelim(&arg); + if (host == NULL) + fatal_f("missing host in %s", what); + host = cleanhostname(host); + if (arg == NULL || ((port = permitopen_port(arg)) < 0)) + fatal_f("bad port number in %s", what); + /* Send it to channels layer */ + channel_add_permission(ssh, FORWARD_ADM, + where, host, port); + free(oarg); + } +} + +/* + * Inform channels layer of permitopen options from configuration. + */ +void +server_process_permitopen(struct ssh *ssh) +{ + server_process_permitopen_list(ssh, 0, + options.permitted_opens, options.num_permitted_opens); + server_process_permitopen_list(ssh, 1, + options.permitted_listens, options.num_permitted_listens); +} + +void +server_process_channel_timeouts(struct ssh *ssh) +{ + u_int i, secs; + char *type; + + debug3_f("setting %u timeouts", options.num_channel_timeouts); + channel_clear_timeouts(ssh); + for (i = 0; i < options.num_channel_timeouts; i++) { + if (parse_pattern_interval(options.channel_timeouts[i], + &type, &secs) != 0) { + fatal_f("internal error: bad timeout %s", + options.channel_timeouts[i]); + } + channel_add_timeout(ssh, type, secs); + free(type); + } +} + +struct connection_info * +server_get_connection_info(struct ssh *ssh, int populate, int use_dns) +{ + static struct connection_info ci; + + if (ssh == NULL || !populate) + return &ci; + ci.host = use_dns ? ssh_remote_hostname(ssh) : ssh_remote_ipaddr(ssh); + ci.address = ssh_remote_ipaddr(ssh); + ci.laddress = ssh_local_ipaddr(ssh); + ci.lport = ssh_local_port(ssh); + ci.rdomain = ssh_packet_rdomain_in(ssh); + return &ci; +} + diff --git a/usr.bin/ssh/monitor_wrap.h b/usr.bin/ssh/monitor_wrap.h index c020c2c8c59..8dbd311d3e7 100644 --- a/usr.bin/ssh/monitor_wrap.h +++ b/usr.bin/ssh/monitor_wrap.h @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor_wrap.h,v 1.49 2022/06/15 16:08:25 djm Exp $ */ +/* $OpenBSD: monitor_wrap.h,v 1.50 2024/05/17 00:30:24 djm Exp $ */ /* * Copyright 2002 Niels Provos @@ -28,9 +28,6 @@ #ifndef _MM_WRAP_H_ #define _MM_WRAP_H_ -extern int use_privsep; -#define PRIVSEP(x) (use_privsep ? mm_##x : x) - enum mm_keytype { MM_NOKEY, MM_HOSTKEY, MM_USERKEY }; struct ssh; @@ -61,6 +58,8 @@ int mm_hostbased_key_allowed(struct ssh *, struct passwd *, const char *, int mm_sshkey_verify(const struct sshkey *, const u_char *, size_t, const u_char *, size_t, const char *, u_int, struct sshkey_sig_details **); +void mm_decode_activate_server_options(struct ssh *ssh, struct sshbuf *m); + #ifdef GSSAPI OM_uint32 mm_ssh_gssapi_server_ctx(Gssctxt **, gss_OID); OM_uint32 mm_ssh_gssapi_accept_ctx(Gssctxt *, @@ -84,4 +83,10 @@ void mm_send_keystate(struct ssh *, struct monitor*); int mm_bsdauth_query(void *, char **, char **, u_int *, char ***, u_int **); int mm_bsdauth_respond(void *, u_int, char **); +/* config / channels glue */ +void server_process_permitopen(struct ssh *); +void server_process_channel_timeouts(struct ssh *ssh); +struct connection_info * + server_get_connection_info(struct ssh *, int, int); + #endif /* _MM_WRAP_H_ */ diff --git a/usr.bin/ssh/msg.c b/usr.bin/ssh/msg.c index 907f233d28e..3aa2d5d8070 100644 --- a/usr.bin/ssh/msg.c +++ b/usr.bin/ssh/msg.c @@ -1,4 +1,4 @@ -/* $OpenBSD: msg.c,v 1.20 2020/10/18 11:32:01 djm Exp $ */ +/* $OpenBSD: msg.c,v 1.21 2024/05/17 00:30:24 djm Exp $ */ /* * Copyright (c) 2002 Markus Friedl. All rights reserved. * @@ -45,7 +45,7 @@ ssh_msg_send(int fd, u_char type, struct sshbuf *m) u_char buf[5]; u_int mlen = sshbuf_len(m); - debug3_f("type %u", (unsigned int)type & 0xff); + debug3_f("type %u len %zu", (unsigned int)type & 0xff, sshbuf_len(m)); put_u32(buf, mlen + 1); buf[4] = type; /* 1st byte of payload is mesg-type */ @@ -57,6 +57,7 @@ ssh_msg_send(int fd, u_char type, struct sshbuf *m) error_f("write: %s", strerror(errno)); return (-1); } + debug3_f("done"); return (0); } diff --git a/usr.bin/ssh/packet.c b/usr.bin/ssh/packet.c index 4ac450d3a57..33e821c23a6 100644 --- a/usr.bin/ssh/packet.c +++ b/usr.bin/ssh/packet.c @@ -1,4 +1,4 @@ -/* $OpenBSD: packet.c,v 1.313 2023/12/18 14:45:17 djm Exp $ */ +/* $OpenBSD: packet.c,v 1.314 2024/05/17 00:30:24 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -514,6 +514,94 @@ ssh_remote_ipaddr(struct ssh *ssh) 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 on 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); + } + + debug3_f("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 diff --git a/usr.bin/ssh/packet.h b/usr.bin/ssh/packet.h index 7dbe682afb7..d3a4c8ae73f 100644 --- a/usr.bin/ssh/packet.h +++ b/usr.bin/ssh/packet.h @@ -1,4 +1,4 @@ -/* $OpenBSD: packet.h,v 1.96 2023/12/18 14:45:17 djm Exp $ */ +/* $OpenBSD: packet.h,v 1.97 2024/05/17 00:30:24 djm Exp $ */ /* * Author: Tatu Ylonen @@ -159,6 +159,7 @@ int ssh_remote_port(struct ssh *); const char *ssh_local_ipaddr(struct ssh *); int ssh_local_port(struct ssh *); const char *ssh_packet_rdomain_in(struct ssh *); +char *ssh_remote_hostname(struct ssh *); void ssh_packet_set_rekey_limits(struct ssh *, u_int64_t, u_int32_t); time_t ssh_packet_get_rekey_timeout(struct ssh *); diff --git a/usr.bin/ssh/pathnames.h b/usr.bin/ssh/pathnames.h index 52d747135d9..0b170505fee 100644 --- a/usr.bin/ssh/pathnames.h +++ b/usr.bin/ssh/pathnames.h @@ -1,4 +1,4 @@ -/* $OpenBSD: pathnames.h,v 1.31 2019/11/12 19:33:08 markus Exp $ */ +/* $OpenBSD: pathnames.h,v 1.32 2024/05/17 00:30:24 djm Exp $ */ /* * Author: Tatu Ylonen @@ -39,6 +39,9 @@ #define _PATH_SSH_PROGRAM "/usr/bin/ssh" +/* Binary paths for the sshd components */ +#define _PATH_SSHD_SESSION "/usr/libexec/sshd-session" + /* * The process id of the daemon listening for connections is saved here to * make it easier to kill the correct daemon when necessary. diff --git a/usr.bin/ssh/servconf.c b/usr.bin/ssh/servconf.c index 85fb86d9228..b8c64bf3b82 100644 --- a/usr.bin/ssh/servconf.c +++ b/usr.bin/ssh/servconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: servconf.c,v 1.405 2024/03/04 02:16:11 djm Exp $ */ +/* $OpenBSD: servconf.c,v 1.406 2024/05/17 00:30:24 djm Exp $ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved @@ -64,8 +64,6 @@ static void parse_server_config_depth(ServerOptions *options, const char *filename, struct sshbuf *conf, struct include_list *includes, struct connection_info *connectinfo, int flags, int *activep, int depth); -/* Use of privilege separation or not */ -extern int use_privsep; extern struct sshbuf *cfg; /* Initializes the server options to their default values. */ @@ -179,6 +177,7 @@ initialize_server_options(ServerOptions *options) options->channel_timeouts = NULL; options->num_channel_timeouts = 0; options->unused_connection_timeout = -1; + options->sshd_session_path = NULL; } /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ @@ -422,13 +421,11 @@ fill_default_server_options(ServerOptions *options) options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE; if (options->unused_connection_timeout == -1) options->unused_connection_timeout = 0; + if (options->sshd_session_path == NULL) + options->sshd_session_path = xstrdup(_PATH_SSHD_SESSION); assemble_algorithms(options); - /* Turn privilege separation and sandboxing on by default */ - if (use_privsep == -1) - use_privsep = PRIVSEP_ON; - #define CLEAR_ON_NONE(v) \ do { \ if (option_clear_or_none(v)) { \ @@ -503,6 +500,7 @@ typedef enum { sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding, sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider, sRequiredRSASize, sChannelTimeout, sUnusedConnectionTimeout, + sSshdMonitorPath, sDeprecated, sIgnore, sUnsupported } ServerOpCodes; @@ -649,6 +647,7 @@ static struct { { "requiredrsasize", sRequiredRSASize, SSHCFG_ALL }, { "channeltimeout", sChannelTimeout, SSHCFG_ALL }, { "unusedconnectiontimeout", sUnusedConnectionTimeout, SSHCFG_ALL }, + { "sshdmonitorpath", sSshdMonitorPath, SSHCFG_GLOBAL }, { NULL, sBadOption, 0 } }; @@ -851,95 +850,6 @@ process_queued_listen_addrs(ServerOptions *options) options->num_queued_listens = 0; } -/* - * Inform channels layer of permitopen options for a single forwarding - * direction (local/remote). - */ -static void -process_permitopen_list(struct ssh *ssh, ServerOpCodes opcode, - char **opens, u_int num_opens) -{ - u_int i; - int port; - char *host, *arg, *oarg; - int where = opcode == sPermitOpen ? FORWARD_LOCAL : FORWARD_REMOTE; - const char *what = lookup_opcode_name(opcode); - - channel_clear_permission(ssh, FORWARD_ADM, where); - if (num_opens == 0) - return; /* permit any */ - - /* handle keywords: "any" / "none" */ - if (num_opens == 1 && strcmp(opens[0], "any") == 0) - return; - if (num_opens == 1 && strcmp(opens[0], "none") == 0) { - channel_disable_admin(ssh, where); - return; - } - /* Otherwise treat it as a list of permitted host:port */ - for (i = 0; i < num_opens; i++) { - oarg = arg = xstrdup(opens[i]); - host = hpdelim(&arg); - if (host == NULL) - fatal_f("missing host in %s", what); - host = cleanhostname(host); - if (arg == NULL || ((port = permitopen_port(arg)) < 0)) - fatal_f("bad port number in %s", what); - /* Send it to channels layer */ - channel_add_permission(ssh, FORWARD_ADM, - where, host, port); - free(oarg); - } -} - -/* - * Inform channels layer of permitopen options from configuration. - */ -void -process_permitopen(struct ssh *ssh, ServerOptions *options) -{ - process_permitopen_list(ssh, sPermitOpen, - options->permitted_opens, options->num_permitted_opens); - process_permitopen_list(ssh, sPermitListen, - options->permitted_listens, - options->num_permitted_listens); -} - -void -process_channel_timeouts(struct ssh *ssh, ServerOptions *options) -{ - int secs; - u_int i; - char *type; - - debug3_f("setting %u timeouts", options->num_channel_timeouts); - channel_clear_timeouts(ssh); - for (i = 0; i < options->num_channel_timeouts; i++) { - if (parse_pattern_interval(options->channel_timeouts[i], - &type, &secs) != 0) { - fatal_f("internal error: bad timeout %s", - options->channel_timeouts[i]); - } - channel_add_timeout(ssh, type, secs); - free(type); - } -} - -struct connection_info * -get_connection_info(struct ssh *ssh, int populate, int use_dns) -{ - static struct connection_info ci; - - if (ssh == NULL || !populate) - return &ci; - ci.host = auth_get_canonical_hostname(ssh, use_dns); - ci.address = ssh_remote_ipaddr(ssh); - ci.laddress = ssh_local_ipaddr(ssh); - ci.lport = ssh_local_port(ssh); - ci.rdomain = ssh_packet_rdomain_in(ssh); - return &ci; -} - /* * The strategy for the Match blocks is that the config file is parsed twice. * @@ -2532,6 +2442,10 @@ process_server_config_line_depth(ServerOptions *options, char *line, } goto parse_time; + case sSshdMonitorPath: + charptr = &options->sshd_session_path; + goto parse_filename; + case sDeprecated: case sIgnore: case sUnsupported: @@ -3097,6 +3011,7 @@ dump_config(ServerOptions *o) dump_cfg_string(sHostKeyAlgorithms, o->hostkeyalgorithms); dump_cfg_string(sPubkeyAcceptedAlgorithms, o->pubkey_accepted_algos); dump_cfg_string(sRDomain, o->routing_domain); + dump_cfg_string(sSshdMonitorPath, o->sshd_session_path); /* string arguments requiring a lookup */ dump_cfg_string(sLogLevel, log_level_name(o->log_level)); diff --git a/usr.bin/ssh/servconf.h b/usr.bin/ssh/servconf.h index bff6f268691..19d6acbd0fa 100644 --- a/usr.bin/ssh/servconf.h +++ b/usr.bin/ssh/servconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: servconf.h,v 1.160 2023/09/06 23:35:35 djm Exp $ */ +/* $OpenBSD: servconf.h,v 1.161 2024/05/17 00:30:24 djm Exp $ */ /* * Author: Tatu Ylonen @@ -27,11 +27,6 @@ #define PERMIT_NO_PASSWD 2 #define PERMIT_YES 3 -/* use_privsep */ -#define PRIVSEP_OFF 0 -#define PRIVSEP_ON 1 -#define PRIVSEP_NOSANDBOX 2 - /* PermitOpen */ #define PERMITOPEN_ANY 0 #define PERMITOPEN_NONE -2 @@ -231,6 +226,8 @@ typedef struct { u_int num_channel_timeouts; int unused_connection_timeout; + + char *sshd_session_path; } ServerOptions; /* Information about the incoming connection as used by Match */ @@ -295,18 +292,16 @@ TAILQ_HEAD(include_list, include_item); M_CP_STRARRAYOPT(subsystem_args, num_subsystems); \ } while (0) -struct connection_info *get_connection_info(struct ssh *, int, int); void initialize_server_options(ServerOptions *); void fill_default_server_options(ServerOptions *); int process_server_config_line(ServerOptions *, char *, const char *, int, int *, struct connection_info *, struct include_list *includes); -void process_permitopen(struct ssh *ssh, ServerOptions *options); -void process_channel_timeouts(struct ssh *ssh, ServerOptions *); void load_server_config(const char *, struct sshbuf *); void parse_server_config(ServerOptions *, const char *, struct sshbuf *, struct include_list *includes, struct connection_info *, int); void parse_server_match_config(ServerOptions *, struct include_list *includes, struct connection_info *); +int parse_channel_timeout(const char *, char **, u_int *); int parse_server_match_testspec(struct connection_info *, char *); int server_match_spec_complete(struct connection_info *); void servconf_merge_subsystems(ServerOptions *, ServerOptions *); diff --git a/usr.bin/ssh/serverloop.c b/usr.bin/ssh/serverloop.c index 2e3101ebf7e..689d21e5263 100644 --- a/usr.bin/ssh/serverloop.c +++ b/usr.bin/ssh/serverloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: serverloop.c,v 1.238 2024/04/30 02:14:10 djm Exp $ */ +/* $OpenBSD: serverloop.c,v 1.239 2024/05/17 00:30:24 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -80,44 +80,23 @@ extern ServerOptions options; /* XXX */ extern Authctxt *the_authctxt; extern struct sshauthopt *auth_opts; -extern int use_privsep; static int no_more_sessions = 0; /* Disallow further sessions. */ static volatile sig_atomic_t child_terminated = 0; /* The child has terminated. */ -/* Cleanup on signals (!use_privsep case only) */ -static volatile sig_atomic_t received_sigterm = 0; - /* prototypes */ static void server_init_dispatch(struct ssh *); /* requested tunnel forwarding interface(s), shared with session.c */ char *tun_fwd_ifnames = NULL; -/* returns 1 if bind to specified port by specified user is permitted */ -static int -bind_permitted(int port, uid_t uid) -{ - if (use_privsep) - return 1; /* allow system to decide */ - if (port < IPPORT_RESERVED && uid != 0) - return 0; - return 1; -} - static void sigchld_handler(int sig) { child_terminated = 1; } -static void -sigterm_handler(int sig) -{ - received_sigterm = sig; -} - static void client_alive_check(struct ssh *ssh) { @@ -348,12 +327,6 @@ server_loop2(struct ssh *ssh, Authctxt *authctxt) connection_in = ssh_packet_get_connection_in(ssh); connection_out = ssh_packet_get_connection_out(ssh); - if (!use_privsep) { - ssh_signal(SIGTERM, sigterm_handler); - ssh_signal(SIGINT, sigterm_handler); - ssh_signal(SIGQUIT, sigterm_handler); - } - server_init_dispatch(ssh); for (;;) { @@ -377,12 +350,6 @@ server_loop2(struct ssh *ssh, Authctxt *authctxt) if (sigprocmask(SIG_SETMASK, &osigset, NULL) == -1) error_f("osigset sigprocmask: %s", strerror(errno)); - if (received_sigterm) { - logit("Exiting on signal %d", (int)received_sigterm); - /* Clean up sessions, utmp, etc. */ - cleanup_exit(255); - } - channel_after_poll(ssh, pfd, npfd_active); if (conn_in_ready && process_input(ssh, connection_in) < 0) @@ -492,7 +459,7 @@ server_request_direct_streamlocal(struct ssh *ssh) /* XXX fine grained permissions */ if ((options.allow_streamlocal_forwarding & FORWARD_LOCAL) != 0 && auth_opts->permit_port_forwarding_flag && - !options.disable_forwarding && (pw->pw_uid == 0 || use_privsep)) { + !options.disable_forwarding) { c = channel_connect_to_path(ssh, target, "direct-streamlocal@openssh.com", "direct-streamlocal"); } else { @@ -781,9 +748,7 @@ server_input_global_request(int type, u_int32_t seq, struct ssh *ssh) (options.allow_tcp_forwarding & FORWARD_REMOTE) == 0 || !auth_opts->permit_port_forwarding_flag || options.disable_forwarding || - (!want_reply && fwd.listen_port == 0) || - (fwd.listen_port != 0 && - !bind_permitted(fwd.listen_port, pw->pw_uid))) { + (!want_reply && fwd.listen_port == 0)) { success = 0; ssh_packet_send_debug(ssh, "Server has disabled port forwarding."); } else { @@ -816,8 +781,7 @@ server_input_global_request(int type, u_int32_t seq, struct ssh *ssh) /* check permissions */ if ((options.allow_streamlocal_forwarding & FORWARD_REMOTE) == 0 || !auth_opts->permit_port_forwarding_flag || - options.disable_forwarding || - (pw->pw_uid != 0 && !use_privsep)) { + options.disable_forwarding) { success = 0; ssh_packet_send_debug(ssh, "Server has disabled " "streamlocal forwarding."); diff --git a/usr.bin/ssh/session.c b/usr.bin/ssh/session.c index 88ebe229a94..e0239a28c0e 100644 --- a/usr.bin/ssh/session.c +++ b/usr.bin/ssh/session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: session.c,v 1.337 2024/02/01 02:37:33 djm Exp $ */ +/* $OpenBSD: session.c,v 1.338 2024/05/17 00:30:24 djm Exp $ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved @@ -705,8 +705,6 @@ do_login(struct ssh *ssh, Session *s, const char *command) { socklen_t fromlen; struct sockaddr_storage from; - struct passwd * pw = s->pw; - pid_t pid = getpid(); /* * Get IP address of client. If the connection is not a socket, let @@ -722,13 +720,6 @@ do_login(struct ssh *ssh, Session *s, const char *command) } } - /* Record that there was a login on that tty from the remote host. */ - if (!use_privsep) - record_login(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); - if (check_quietlogin(s, command)) return; @@ -1606,8 +1597,7 @@ session_pty_req(struct ssh *ssh, Session *s) /* Allocate a pty and open it. */ debug("Allocating pty."); - if (!PRIVSEP(pty_allocate(&s->ptyfd, &s->ttyfd, s->tty, - sizeof(s->tty)))) { + if (!mm_pty_allocate(&s->ptyfd, &s->ttyfd, s->tty, sizeof(s->tty))) { free(s->term); s->term = NULL; s->ptyfd = -1; @@ -1622,9 +1612,6 @@ session_pty_req(struct ssh *ssh, Session *s) if ((r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); - if (!use_privsep) - pty_setowner(s->pw, s->tty); - /* Set window size from the packet. */ pty_change_window_size(s->ptyfd, s->row, s->col, s->xpixel, s->ypixel); @@ -1840,7 +1827,7 @@ session_signal_req(struct ssh *ssh, Session *s) signame, s->forced ? "forced-command" : "subsystem"); goto out; } - if (!use_privsep || mm_is_monitor()) { + if (mm_is_monitor()) { error_f("session signalling requires privilege separation"); goto out; } @@ -1983,7 +1970,7 @@ session_pty_cleanup2(Session *s) void session_pty_cleanup(Session *s) { - PRIVSEP(session_pty_cleanup2(s)); + mm_session_pty_cleanup2(s); } static char * @@ -2354,7 +2341,7 @@ do_cleanup(struct ssh *ssh, Authctxt *authctxt) * Cleanup ptys/utmp only if privsep is disabled, * or if running in monitor. */ - if (!use_privsep || mm_is_monitor()) + if (mm_is_monitor()) session_destroy_all(ssh, session_pty_cleanup2); } diff --git a/usr.bin/ssh/ssh_api.c b/usr.bin/ssh/ssh_api.c index 54f82cfb7c8..e496403f975 100644 --- a/usr.bin/ssh/ssh_api.c +++ b/usr.bin/ssh/ssh_api.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh_api.c,v 1.28 2024/01/09 21:39:14 djm Exp $ */ +/* $OpenBSD: ssh_api.c,v 1.29 2024/05/17 00:30:24 djm Exp $ */ /* * Copyright (c) 2012 Markus Friedl. All rights reserved. * @@ -25,6 +25,7 @@ #include "log.h" #include "authfile.h" #include "sshkey.h" +#include "dh.h" #include "misc.h" #include "ssh2.h" #include "version.h" @@ -45,10 +46,8 @@ int _ssh_host_key_sign(struct ssh *, struct sshkey *, struct sshkey *, u_char **, size_t *, const u_char *, size_t, const char *); /* - * stubs for the server side implementation of kex. - * disable privsep so our stubs will never be called. + * stubs for privsep calls in the server side implementation of kex. */ -int use_privsep = 0; int mm_sshkey_sign(struct sshkey *, u_char **, u_int *, const u_char *, u_int, const char *, const char *, const char *, u_int); @@ -61,14 +60,20 @@ mm_sshkey_sign(struct sshkey *key, u_char **sigp, u_int *lenp, const u_char *data, u_int datalen, const char *alg, const char *sk_provider, const char *sk_pin, u_int compat) { - return (-1); + size_t slen = 0; + int ret; + + ret = sshkey_sign(key, sigp, &slen, data, datalen, alg, + sk_provider, sk_pin, compat); + *lenp = slen; + return ret; } #ifdef WITH_OPENSSL DH * mm_choose_dh(int min, int nbits, int max) { - return (NULL); + return choose_dh(min, nbits, max); } #endif diff --git a/usr.bin/ssh/sshd-session.c b/usr.bin/ssh/sshd-session.c new file mode 100644 index 00000000000..820e3cb1f37 --- /dev/null +++ b/usr.bin/ssh/sshd-session.c @@ -0,0 +1,1354 @@ +/* $OpenBSD: sshd-session.c,v 1.1 2024/05/17 00:30:24 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WITH_OPENSSL +#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 "ssh-sandbox.h" +#include "auth-options.h" +#include "version.h" +#include "ssherr.h" +#include "sk-api.h" +#include "srclimit.h" +#include "dh.h" + +/* Re-exec fds */ +#define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) +#define REEXEC_STARTUP_PIPE_FD (STDERR_FILENO + 2) +#define REEXEC_CONFIG_PASS_FD (STDERR_FILENO + 3) +#define REEXEC_MIN_FREE_FD (STDERR_FILENO + 4) + +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; + +/* 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; + +/* 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); +static void do_ssh2_kex(struct ssh *); + +/* + * Signal handler for the alarm after the login grace period has expired. + */ +static void +grace_alarm_handler(int sig) +{ + /* + * Try to kill any processes that we have spawned, E.g. authorized + * keys command helpers or privsep children. + */ + if (getpgid(0) == getpid()) { + ssh_signal(SIGTERM, SIG_IGN); + kill(0, SIGTERM); + } + + /* Log error and exit. */ + sigdie("Timeout before authentication for %s port %d", + ssh_remote_ipaddr(the_active_state), + ssh_remote_port(the_active_state)); +} + +/* 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 +privsep_preauth_child(void) +{ + gid_t gidset[1]; + struct passwd *pw; + + /* Enable challenge-response authentication for privilege separation */ + privsep_challenge_enable(); + +#ifdef GSSAPI + /* Cache supported mechanism OIDs for later use */ + ssh_gssapi_prepare_supported_oids(); +#endif + + /* Demote the private keys to public keys. */ + demote_sensitive_data(); + + /* Demote the child */ + if (getuid() == 0 || geteuid() == 0) { + if ((pw = getpwnam(SSH_PRIVSEP_USER)) == NULL) + fatal("Privilege separation user %s does not exist", + SSH_PRIVSEP_USER); + pw = pwcopy(pw); /* Ensure mutable */ + endpwent(); + freezero(pw->pw_passwd, strlen(pw->pw_passwd)); + + /* Change our root directory */ + if (chroot(_PATH_PRIVSEP_CHROOT_DIR) == -1) + fatal("chroot(\"%s\"): %s", _PATH_PRIVSEP_CHROOT_DIR, + strerror(errno)); + if (chdir("/") == -1) + fatal("chdir(\"/\"): %s", strerror(errno)); + + /* + * Drop our privileges + * NB. Can't use setusercontext() after chroot. + */ + debug3("privsep user:group %u:%u", (u_int)pw->pw_uid, + (u_int)pw->pw_gid); + gidset[0] = pw->pw_gid; + if (setgroups(1, gidset) == -1) + fatal("setgroups: %.100s", strerror(errno)); + permanently_set_uid(pw); + } +} + +static int +privsep_preauth(struct ssh *ssh) +{ + int status, r; + pid_t pid; + struct ssh_sandbox *box = NULL; + + /* 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; + + box = ssh_sandbox_init(); + pid = fork(); + if (pid == -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; + } + } + if (box != NULL) + ssh_sandbox_parent_preauth(box, pid); + 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)); + if (box != NULL) + ssh_sandbox_parent_finish(box); + return 1; + } else { + /* child */ + close(pmonitor->m_sendfd); + close(pmonitor->m_log_recvfd); + + /* Arrange for logging to be sent to the monitor */ + set_log_handler(mm_log_handler, pmonitor); + + privsep_preauth_child(); + setproctitle("%s", "[net]"); + if (box != NULL) + ssh_sandbox_child(box); + + return 0; + } +} + +static void +privsep_postauth(struct ssh *ssh, Authctxt *authctxt) +{ + /* 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(); + + /* Drop privileges */ + 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 void +append_hostkey_type(struct sshbuf *b, const char *s) +{ + int r; + + if (match_pattern_list(s, options.hostkeyalgorithms, 0) != 1) { + debug3_f("%s key not permitted by HostkeyAlgorithms", s); + return; + } + if ((r = sshbuf_putf(b, "%s%s", sshbuf_len(b) > 0 ? "," : "", s)) != 0) + fatal_fr(r, "sshbuf_putf"); +} + +static char * +list_hostkey_types(void) +{ + struct sshbuf *b; + struct sshkey *key; + char *ret; + u_int i; + + if ((b = sshbuf_new()) == NULL) + fatal_f("sshbuf_new failed"); + for (i = 0; i < options.num_host_key_files; i++) { + key = sensitive_data.host_keys[i]; + if (key == NULL) + key = sensitive_data.host_pubkeys[i]; + if (key == NULL) + continue; + switch (key->type) { + case KEY_RSA: + /* for RSA we also support SHA2 signatures */ + append_hostkey_type(b, "rsa-sha2-512"); + append_hostkey_type(b, "rsa-sha2-256"); + /* FALLTHROUGH */ + case KEY_DSA: + case KEY_ECDSA: + case KEY_ED25519: + case KEY_ECDSA_SK: + case KEY_ED25519_SK: + case KEY_XMSS: + append_hostkey_type(b, sshkey_ssh_name(key)); + break; + } + /* If the private key has a cert peer, then list that too */ + key = sensitive_data.host_certificates[i]; + if (key == NULL) + continue; + switch (key->type) { + case KEY_RSA_CERT: + /* for RSA we also support SHA2 signatures */ + append_hostkey_type(b, + "rsa-sha2-512-cert-v01@openssh.com"); + append_hostkey_type(b, + "rsa-sha2-256-cert-v01@openssh.com"); + /* FALLTHROUGH */ + 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: + append_hostkey_type(b, sshkey_ssh_name(key)); + break; + } + } + if ((ret = sshbuf_dup_string(b)) == NULL) + fatal_f("sshbuf_dup_string failed"); + sshbuf_free(b); + debug_f("%s", ret); + return ret; +} + +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_VERSION, 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"); + 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_froms(m, &hostkeys)) != 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); + } + + 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) +{ + 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); + } + return; +} + +/* Set the routing domain for this process */ +static void +set_process_rdomain(struct ssh *ssh, const char *name) +{ + 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); +} + +/* + * Main program for the daemon. + */ +int +main(int ac, char **av) +{ + struct ssh *ssh = NULL; + extern char *optarg; + extern int optind; + int 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; + + sigemptyset(&sigmask); + sigprocmask(SIG_SETMASK, &sigmask, NULL); + + /* Save argv. */ + saved_argv = av; + + /* 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_VERSION, 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); + } + + debug("sshd version %s, %s", SSH_VERSION, SSH_OPENSSL_VERSION); + + if (!rexeced_flag) + fatal("sshd-session should not be executed directly"); + + closefrom(REEXEC_MIN_FREE_FD); + +#ifdef WITH_OPENSSL + OpenSSL_add_all_algorithms(); +#endif + + /* 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); + + debug("sshd version %s, %s", SSH_VERSION, SSH_OPENSSL_VERSION); + + /* 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); + close(REEXEC_CONFIG_PASS_FD); + 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; + + if (!debug_flag) { + startup_pipe = dup(REEXEC_STARTUP_PIPE_FD); + close(REEXEC_STARTUP_PIPE_FD); + /* + * 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); + } + + /* 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 recieved 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); + + /* + * 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); + } + + /* + * 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); + + 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) + alarm(options.login_grace_time); + + if ((r = kex_exchange_identification(ssh, -1, + options.version_addendum)) != 0) + 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(); + + if (privsep_preauth(ssh) == 1) + goto authenticated; + + /* perform the key exchange */ + /* authenticate user and start session */ + do_ssh2_kex(ssh); + do_authentication2(ssh); + + /* + * The unprivileged child now transfers the current keystate and exits. + */ + mm_send_keystate(ssh, pmonitor); + ssh_packet_clear_keys(ssh); + exit(0); + + authenticated: + /* + * Cancel the alarm we set to limit the time taken for + * authentication. + */ + alarm(0); + ssh_signal(SIGALRM, SIG_DFL); + authctxt->authenticated = 1; + if (startup_pipe != -1) { + close(startup_pipe); + startup_pipe = -1; + } + + if (options.routing_domain != NULL) + set_process_rdomain(ssh, options.routing_domain); + + /* + * 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); + 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; +} + +/* SSH2 key exchange */ +static void +do_ssh2_kex(struct ssh *ssh) +{ + char *hkalgs = NULL, *myproposal[PROPOSAL_MAX]; + const char *compression = NULL; + struct kex *kex; + int r; + + if (options.rekey_limit || options.rekey_interval) + ssh_packet_set_rekey_limits(ssh, options.rekey_limit, + options.rekey_interval); + + if (options.compression == COMP_NONE) + compression = "none"; + hkalgs = list_hostkey_types(); + + kex_proposal_populate_entries(ssh, myproposal, options.kex_algorithms, + options.ciphers, options.macs, compression, hkalgs); + + free(hkalgs); + + /* start key exchange */ + if ((r = kex_setup(ssh, myproposal)) != 0) + fatal_r(r, "kex_setup"); + kex_set_server_sig_algs(ssh, options.pubkey_accepted_algos); + kex = ssh->kex; + +#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; + kex->kex[KEX_ECDH_SHA2] = kex_gen_server; +#endif + kex->kex[KEX_C25519_SHA256] = kex_gen_server; + kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = 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; + + ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &kex->done); + kex_proposal_free_entries(myproposal); + +#ifdef DEBUG_KEXDH + /* send 1st encrypted/maced/compressed message */ + if ((r = sshpkt_start(ssh, SSH2_MSG_IGNORE)) != 0 || + (r = sshpkt_put_cstring(ssh, "markus")) != 0 || + (r = sshpkt_send(ssh)) != 0 || + (r = ssh_packet_write_wait(ssh)) != 0) + fatal_fr(r, "send test"); +#endif + debug("KEX done"); +} + +/* server specific fatal cleanup */ +void +cleanup_exit(int i) +{ + 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)); + } + } + } + _exit(i); +} diff --git a/usr.bin/ssh/sshd-session/Makefile b/usr.bin/ssh/sshd-session/Makefile new file mode 100644 index 00000000000..0ca4add8195 --- /dev/null +++ b/usr.bin/ssh/sshd-session/Makefile @@ -0,0 +1,71 @@ +# $OpenBSD: Makefile,v 1.1 2024/05/17 00:30:24 djm Exp $ + +.PATH: ${.CURDIR}/.. + +SRCS= sshd-session.c auth2-methods.c \ + auth-rhosts.c auth-passwd.c sshpty.c sshlogin.c servconf.c \ + serverloop.c auth.c auth2.c auth-options.c session.c auth2-chall.c \ + groupaccess.c auth-bsdauth.c auth2-hostbased.c auth2-kbdint.c \ + auth2-none.c auth2-passwd.c auth2-pubkey.c auth2-pubkeyfile.c \ + monitor.c monitor_wrap.c \ + sftp-server.c sftp-common.c sftp-realpath.c sandbox-pledge.c srclimit.c +SRCS+= authfd.c compat.c dns.c fatal.c hostfile.c readpass.c utf8.c uidswap.c +SRCS+= ${SRCS_BASE} ${SRCS_KEX} ${SRCS_KEXS} ${SRCS_KEY} ${SRCS_KEYP} \ + ${SRCS_KRL} ${SRCS_PROT} ${SRCS_PKT} ${SRCS_UTL} ${SRCS_PKCS11} \ + ${SRCS_SK_CLIENT} + +PROG= sshd-session +BINDIR= /usr/libexec +BINMODE=511 +NOMAN= 1 + +.include # for KERBEROS and AFS + +KERBEROS5=no + +.if (${KERBEROS5:L} == "yes") +CFLAGS+=-DKRB5 -I${DESTDIR}/usr/include/kerberosV -DGSSAPI +SRCS+= auth-krb5.c auth2-gss.c gss-serv.c gss-serv-krb5.c +.endif + +.include + +.if (${KERBEROS5:L} == "yes") +LDADD+= -lgssapi -lkrb5 -lasn1 +LDADD+= -lwind -lroken -lcom_err -lpthread -lheimbase -lkafs +DPADD+= ${LIBGSSAPI} ${LIBKRB5} +.endif + +.if (${OPENSSL:L} == "yes") +LDADD+= -lcrypto +DPADD+= ${LIBCRYPTO} +.endif + +LDADD+= -lutil +DPADD+= ${LIBUTIL} + +.if (${ZLIB:L} == "yes") +LDADD+= -lz +DPADD+= ${LIBZ} +.endif + +# The random relink kit, used on OpenBSD by /etc/rc + +CLEANFILES+= ${PROG}.tar install.sh + +install.sh: Makefile + echo "set -o errexit" > $@ + echo "${CC} ${LDFLAGS} ${LDSTATIC} -o ${PROG}" \ + "\`echo " ${OBJS} "| tr ' ' '\\\n' | sort -R\`" ${LDADD} >> $@ + echo "./${PROG} -V # test it works" >> $@ + echo "install -c -s -o root -g bin -m ${BINMODE} ${PROG} " \ + "${BINDIR}/${PROG}" >> $@ + +${PROG}.tar: ${OBJS} install.sh + tar cf $@ ${OBJS} install.sh + +afterinstall: ${PROG}.tar + install -d -o root -g wheel -m 755 \ + ${DESTDIR}/usr/share/relink/${BINDIR}/${PROG} + install -o ${BINOWN} -g ${BINGRP} -m 640 \ + ${PROG}.tar ${DESTDIR}/usr/share/relink/${BINDIR}/${PROG}/${PROG}.tar diff --git a/usr.bin/ssh/sshd.c b/usr.bin/ssh/sshd.c index a5d56e9fa01..683b0b885be 100644 --- a/usr.bin/ssh/sshd.c +++ b/usr.bin/ssh/sshd.c @@ -1,23 +1,5 @@ -/* $OpenBSD: sshd.c,v 1.602 2024/01/08 00:34:34 djm Exp $ */ +/* $OpenBSD: sshd.c,v 1.603 2024/05/17 00:30:24 djm Exp $ */ /* - * Author: Tatu Ylonen - * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland - * All rights reserved - * This program is the ssh daemon. It listens for connections from clients, - * and performs authentication, executes use commands or shell, and forwards - * information to/from the application to the user client over an encrypted - * connection. This can also handle forwarding of X11, TCP/IP, and - * authentication agent connections. - * - * 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 implementation: - * Privilege Separation: - * * Copyright (c) 2000, 2001, 2002 Markus Friedl. All rights reserved. * Copyright (c) 2002 Niels Provos. All rights reserved. * @@ -67,47 +49,30 @@ #ifdef WITH_OPENSSL #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 "ssh-sandbox.h" -#include "auth-options.h" #include "version.h" #include "ssherr.h" #include "sk-api.h" #include "srclimit.h" -#include "dh.h" /* Re-exec fds */ #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) @@ -120,9 +85,6 @@ 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 @@ -131,32 +93,9 @@ char *config_file_name = _PATH_SERVER_CONFIG_FILE; */ int debug_flag = 0; -/* - * Indicating that the daemon should only test the configuration and keys. - * If test_flag > 1 ("-T" flag), then sshd will also dump the effective - * configuration, optionally using connection information provided by the - * "-C" flag. - */ -static int test_flag = 0; - -/* Flag indicating that the daemon is being started from inetd. */ -static int inetd_flag = 0; - -/* Flag indicating that sshd should not detach and become a daemon. */ -static int no_daemon_flag = 0; - -/* debug goes to stderr unless inetd_flag is set */ -static int log_stderr = 0; - /* Saved arguments to main(). */ static char **saved_argv; -/* re-exec */ -static int rexeced_flag = 0; -static int rexec_flag = 1; -static int rexec_argc = 0; -static char **rexec_argv; - /* * The sockets that the server is listening; this is used in the SIGHUP * signal handler. @@ -165,10 +104,6 @@ static char **rexec_argv; static int listen_socks[MAX_LISTEN_SOCKS]; static int num_listen_socks = 0; -/* 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 @@ -212,18 +147,6 @@ static int *startup_pipes = NULL; static int *startup_flags = NULL; /* Indicates child closed listener */ static int startup_pipe = -1; /* in child */ -/* variables used for privilege separation */ -int use_privsep = -1; -struct monitor *pmonitor = NULL; -int privsep_is_preauth = 1; - -/* 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; @@ -233,11 +156,6 @@ 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); -static void do_ssh2_kex(struct ssh *); - static char *listener_proctitle; /* @@ -321,446 +239,6 @@ main_sigchld_handler(int sig) errno = save_errno; } -/* - * Signal handler for the alarm after the login grace period has expired. - */ -static void -grace_alarm_handler(int sig) -{ - /* - * Try to kill any processes that we have spawned, E.g. authorized - * keys command helpers or privsep children. - */ - if (getpgid(0) == getpid()) { - ssh_signal(SIGTERM, SIG_IGN); - kill(0, SIGTERM); - } - - /* Log error and exit. */ - sigdie("Timeout before authentication for %s port %d", - ssh_remote_ipaddr(the_active_state), - ssh_remote_port(the_active_state)); -} - -/* 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 -privsep_preauth_child(void) -{ - gid_t gidset[1]; - struct passwd *pw; - - /* Enable challenge-response authentication for privilege separation */ - privsep_challenge_enable(); - -#ifdef GSSAPI - /* Cache supported mechanism OIDs for later use */ - ssh_gssapi_prepare_supported_oids(); -#endif - - /* Demote the private keys to public keys. */ - demote_sensitive_data(); - - /* Demote the child */ - if (getuid() == 0 || geteuid() == 0) { - if ((pw = getpwnam(SSH_PRIVSEP_USER)) == NULL) - fatal("Privilege separation user %s does not exist", - SSH_PRIVSEP_USER); - pw = pwcopy(pw); /* Ensure mutable */ - endpwent(); - freezero(pw->pw_passwd, strlen(pw->pw_passwd)); - - /* Change our root directory */ - if (chroot(_PATH_PRIVSEP_CHROOT_DIR) == -1) - fatal("chroot(\"%s\"): %s", _PATH_PRIVSEP_CHROOT_DIR, - strerror(errno)); - if (chdir("/") == -1) - fatal("chdir(\"/\"): %s", strerror(errno)); - - /* - * Drop our privileges - * NB. Can't use setusercontext() after chroot. - */ - debug3("privsep user:group %u:%u", (u_int)pw->pw_uid, - (u_int)pw->pw_gid); - gidset[0] = pw->pw_gid; - if (setgroups(1, gidset) == -1) - fatal("setgroups: %.100s", strerror(errno)); - permanently_set_uid(pw); - } -} - -static int -privsep_preauth(struct ssh *ssh) -{ - int status, r; - pid_t pid; - struct ssh_sandbox *box = NULL; - - /* 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 (use_privsep == PRIVSEP_ON) - box = ssh_sandbox_init(); - pid = fork(); - if (pid == -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; - } - } - if (box != NULL) - ssh_sandbox_parent_preauth(box, pid); - 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)); - if (box != NULL) - ssh_sandbox_parent_finish(box); - return 1; - } else { - /* child */ - close(pmonitor->m_sendfd); - close(pmonitor->m_log_recvfd); - - /* Arrange for logging to be sent to the monitor */ - set_log_handler(mm_log_handler, pmonitor); - - privsep_preauth_child(); - setproctitle("%s", "[net]"); - if (box != NULL) - ssh_sandbox_child(box); - - return 0; - } -} - -static void -privsep_postauth(struct ssh *ssh, Authctxt *authctxt) -{ - if (authctxt->pw->pw_uid == 0) { - /* File descriptor passing is broken or root login */ - use_privsep = 0; - goto skip; - } - - /* 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(); - - /* Drop privileges */ - do_setusercontext(authctxt->pw); - - skip: - /* 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 void -append_hostkey_type(struct sshbuf *b, const char *s) -{ - int r; - - if (match_pattern_list(s, options.hostkeyalgorithms, 0) != 1) { - debug3_f("%s key not permitted by HostkeyAlgorithms", s); - return; - } - if ((r = sshbuf_putf(b, "%s%s", sshbuf_len(b) > 0 ? "," : "", s)) != 0) - fatal_fr(r, "sshbuf_putf"); -} - -static char * -list_hostkey_types(void) -{ - struct sshbuf *b; - struct sshkey *key; - char *ret; - u_int i; - - if ((b = sshbuf_new()) == NULL) - fatal_f("sshbuf_new failed"); - for (i = 0; i < options.num_host_key_files; i++) { - key = sensitive_data.host_keys[i]; - if (key == NULL) - key = sensitive_data.host_pubkeys[i]; - if (key == NULL) - continue; - switch (key->type) { - case KEY_RSA: - /* for RSA we also support SHA2 signatures */ - append_hostkey_type(b, "rsa-sha2-512"); - append_hostkey_type(b, "rsa-sha2-256"); - /* FALLTHROUGH */ - case KEY_DSA: - case KEY_ECDSA: - case KEY_ED25519: - case KEY_ECDSA_SK: - case KEY_ED25519_SK: - case KEY_XMSS: - append_hostkey_type(b, sshkey_ssh_name(key)); - break; - } - /* If the private key has a cert peer, then list that too */ - key = sensitive_data.host_certificates[i]; - if (key == NULL) - continue; - switch (key->type) { - case KEY_RSA_CERT: - /* for RSA we also support SHA2 signatures */ - append_hostkey_type(b, - "rsa-sha2-512-cert-v01@openssh.com"); - append_hostkey_type(b, - "rsa-sha2-256-cert-v01@openssh.com"); - /* FALLTHROUGH */ - 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: - append_hostkey_type(b, sshkey_ssh_name(key)); - break; - } - } - if ((ret = sshbuf_dup_string(b)) == NULL) - fatal_f("sshbuf_dup_string failed"); - sshbuf_free(b); - debug_f("%s", ret); - return ret; -} - -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); -} - /* * returns 1 if connection should be dropped, 0 otherwise. * dropping starts at connection #max_startups_begin with a probability @@ -860,17 +338,64 @@ usage(void) exit(1); } +static struct sshbuf * +pack_hostkeys(void) +{ + struct sshbuf *keybuf = NULL, *hostkeys = NULL; + int r; + u_int i; + + if ((keybuf = sshbuf_new()) == NULL || + (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++) { + /* private key */ + sshbuf_reset(keybuf); + if (sensitive_data.host_keys[i] != NULL && + (r = sshkey_private_serialize(sensitive_data.host_keys[i], + keybuf)) != 0) + fatal_fr(r, "serialize hostkey private"); + if ((r = sshbuf_put_stringb(hostkeys, keybuf)) != 0) + fatal_fr(r, "compose hostkey private"); + /* 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 void send_rexec_state(int fd, struct sshbuf *conf) { - struct sshbuf *m = NULL, *inc = NULL; + struct sshbuf *m = NULL, *inc = NULL, *hostkeys = NULL; struct include_item *item = NULL; - int r; + int r, sz; debug3_f("entering fd = %d config len %zu", fd, sshbuf_len(conf)); - if ((m = sshbuf_new()) == NULL || (inc = sshbuf_new()) == NULL) + if ((m = sshbuf_new()) == NULL || + (inc = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); /* pack includes into a string */ @@ -881,9 +406,17 @@ send_rexec_state(int fd, struct sshbuf *conf) fatal_fr(r, "compose includes"); } + hostkeys = pack_hostkeys(); + /* * Protocol from reexec master to child: * string configuration + * uint64 timing_secret + * string host_keys[] { + * string private_key + * string public_key + * string certificate + * } * string included_files[] { * string selector * string filename @@ -891,81 +424,26 @@ send_rexec_state(int fd, struct sshbuf *conf) * } */ if ((r = sshbuf_put_stringb(m, conf)) != 0 || + (r = sshbuf_put_u64(m, options.timing_secret)) != 0 || + (r = sshbuf_put_stringb(m, hostkeys)) != 0 || (r = sshbuf_put_stringb(m, inc)) != 0) fatal_fr(r, "compose config"); + + /* We need to fit the entire message inside the socket send buffer */ + sz = ROUNDUP(sshbuf_len(m) + 5, 16*1024); + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sz, sizeof sz) == -1) + fatal_f("setsockopt SO_SNDBUF: %s", strerror(errno)); + if (ssh_msg_send(fd, 0, m) == -1) error_f("ssh_msg_send failed"); sshbuf_free(m); sshbuf_free(inc); + sshbuf_free(hostkeys); debug3_f("done"); } -static void -recv_rexec_state(int fd, struct sshbuf *conf) -{ - struct sshbuf *m, *inc; - 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"); - 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 || - (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); - } - - free(cp); - sshbuf_free(m); - - debug3_f("done"); -} - -/* Accept a connection from inetd */ -static void -server_accept_inetd(int *sock_in, int *sock_out) -{ - if (rexeced_flag) { - close(REEXEC_CONFIG_PASS_FD); - *sock_in = *sock_out = dup(STDIN_FILENO); - } else { - *sock_in = dup(STDIN_FILENO); - *sock_out = dup(STDOUT_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_f("stdfd_devnull failed"); - debug("inetd sockets after dupping: %d, %d", *sock_in, *sock_out); -} - /* * Listen for TCP connections */ @@ -1066,7 +544,8 @@ server_listen(void) * from this function are in a forked subprocess. */ static void -server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) +server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s, + int log_stderr) { struct pollfd *pfd = NULL; int i, j, ret, npfd; @@ -1223,7 +702,7 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) continue; } - if (rexec_flag && socketpair(AF_UNIX, + if (socketpair(AF_UNIX, SOCK_STREAM, 0, config_s) == -1) { error("reexec socketpair: %s", strerror(errno)); @@ -1259,10 +738,8 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) close(startup_p[1]); startup_pipe = -1; pid = getpid(); - if (rexec_flag) { - send_rexec_state(config_s[0], cfg); - close(config_s[0]); - } + send_rexec_state(config_s[0], cfg); + close(config_s[0]); free(pfd); return; } @@ -1291,19 +768,7 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) options.log_level, options.log_facility, log_stderr); - if (rexec_flag) - close(config_s[0]); - else { - /* - * Signal parent that the preliminaries - * for this child are complete. For the - * re-exec case, this happens after the - * child has received the rexec state - * from the server. - */ - (void)atomicio(vwrite, startup_pipe, - "\0", 1); - } + close(config_s[0]); free(pfd); return; } @@ -1316,81 +781,14 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) close(startup_p[1]); - if (rexec_flag) { - close(config_s[1]); - send_rexec_state(config_s[0], cfg); - close(config_s[0]); - } + close(config_s[1]); + send_rexec_state(config_s[0], cfg); + close(config_s[0]); close(*newsock); } } } -/* - * 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) -{ - 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); - } - return; -} - -/* Set the routing domain for this process */ -static void -set_process_rdomain(struct ssh *ssh, const char *name) -{ - 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); -} - static void accumulate_host_timing_secret(struct sshbuf *server_cfg, struct sshkey *key) @@ -1440,14 +838,8 @@ prepare_proctitle(int ac, char **av) } static void -print_config(struct ssh *ssh, struct connection_info *connection_info) +print_config(struct connection_info *connection_info) { - /* - * If no connection info was provided by -C then use - * use a blank one that will cause no predicate to match. - */ - if (connection_info == NULL) - connection_info = get_connection_info(ssh, 0, 0); connection_info->test = 1; parse_server_match_config(&options, &includes, connection_info); dump_config(&options); @@ -1460,24 +852,24 @@ print_config(struct ssh *ssh, struct connection_info *connection_info) int main(int ac, char **av) { - struct ssh *ssh = NULL; extern char *optarg; extern int optind; - int r, opt, on = 1, do_dump_cfg = 0, already_daemon, remote_port; - int sock_in = -1, sock_out = -1, newsock = -1; - const char *remote_ip, *rdomain; - char *fp, *line, *laddr, *logfile = NULL; - int config_s[2] = { -1 , -1 }; + int log_stderr = 0, inetd_flag = 0, test_flag = 0, no_daemon_flag = 0; + char *config_file_name = _PATH_SERVER_CONFIG_FILE; + int r, opt, do_dump_cfg = 0, keytype, already_daemon, have_agent = 0; + int sock_in = -1, sock_out = -1, newsock = -1, rexec_argc = 0; + int config_s[2] = { -1 , -1 }, have_connection_info = 0; + char *fp, *line, *logfile = NULL, **rexec_argv = NULL; + struct stat sb; u_int i, j; - u_int64_t ibytes, obytes; mode_t new_umask; struct sshkey *key; struct sshkey *pubkey; - int keytype; - Authctxt *authctxt; - struct connection_info *connection_info = NULL; + struct connection_info connection_info; sigset_t sigmask; + memset(&connection_info, 0, sizeof(connection_info)); + sigemptyset(&sigmask); sigprocmask(SIG_SETMASK, &sigmask, NULL); @@ -1531,11 +923,10 @@ main(int ac, char **av) inetd_flag = 1; break; case 'r': - rexec_flag = 0; + /* ignored */ break; case 'R': - rexeced_flag = 1; - inetd_flag = 1; + fatal("-R not supported here"); break; case 'Q': /* ignored */ @@ -1578,10 +969,10 @@ main(int ac, char **av) test_flag = 2; break; case 'C': - connection_info = get_connection_info(ssh, 0, 0); - if (parse_server_match_testspec(connection_info, + if (parse_server_match_testspec(&connection_info, optarg) == -1) exit(1); + have_connection_info = 1; break; case 'u': utmp_len = (u_int)strtonum(optarg, 0, HOST_NAME_MAX+1+1, NULL); @@ -1606,22 +997,27 @@ main(int ac, char **av) break; } } - if (rexeced_flag || inetd_flag) - rexec_flag = 0; - if (!test_flag && !do_dump_cfg && rexec_flag && !path_absolute(av[0])) - fatal("sshd re-exec requires execution with an absolute path"); - if (rexeced_flag) - closefrom(REEXEC_MIN_FREE_FD); - else - closefrom(REEXEC_DEVCRYPTO_RESERVED_FD); + if (!test_flag && !do_dump_cfg && !path_absolute(av[0])) + fatal("sshd requires execution with an absolute path"); + closefrom(REEXEC_DEVCRYPTO_RESERVED_FD); #ifdef WITH_OPENSSL OpenSSL_add_all_algorithms(); #endif /* If requested, redirect the logs to the specified logfile. */ - if (logfile != NULL) - log_redirect_stderr_to(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", + (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) @@ -1639,35 +1035,18 @@ main(int ac, char **av) * If we're not doing an extended test do not silently ignore connection * test params. */ - if (test_flag < 2 && connection_info != NULL) + if (test_flag < 2 && have_connection_info) fatal("Config test connection parameter (-C) provided without " "test mode (-T)"); /* Fetch our configuration */ if ((cfg = sshbuf_new()) == NULL) - fatal_f("sshbuf_new failed"); - if (rexeced_flag) { - setproctitle("%s", "[rexeced]"); - recv_rexec_state(REEXEC_CONFIG_PASS_FD, cfg); - if (!debug_flag) { - startup_pipe = dup(REEXEC_STARTUP_PIPE_FD); - close(REEXEC_STARTUP_PIPE_FD); - /* - * 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); - } - } else if (strcasecmp(config_file_name, "none") != 0) + fatal("sshbuf_new config failed"); + if (strcasecmp(config_file_name, "none") != 0) load_server_config(config_file_name, cfg); - parse_server_config(&options, rexeced_flag ? "rexec" : config_file_name, - cfg, &includes, NULL, rexeced_flag); - -#ifdef WITH_OPENSSL - if (options.moduli_file != NULL) - dh_set_moduli_file(options.moduli_file); -#endif + parse_server_config(&options, config_file_name, cfg, + &includes, NULL, 0); /* Fill in default values for those options not explicitly set. */ fill_default_server_options(&options); @@ -1687,7 +1066,7 @@ main(int ac, char **av) /* * 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 + * daemonisation in the presence of Match blocks, but this catches * and warns for trivial misconfigurations that could break login. */ if (options.num_auth_methods != 0) { @@ -1710,7 +1089,7 @@ main(int ac, char **av) debug("sshd version %s, %s", SSH_VERSION, SSH_OPENSSL_VERSION); if (do_dump_cfg) - print_config(ssh, connection_info); + print_config(&connection_info); /* load host keys */ sensitive_data.host_keys = xcalloc(options.num_host_key_files, @@ -1862,40 +1241,42 @@ main(int ac, char **av) sshkey_type(key)); } - if (use_privsep) { - struct stat st; - - if (getpwnam(SSH_PRIVSEP_USER) == NULL) - fatal("Privilege separation user %s does not exist", - SSH_PRIVSEP_USER); - endpwent(); - if ((stat(_PATH_PRIVSEP_CHROOT_DIR, &st) == -1) || - (S_ISDIR(st.st_mode) == 0)) - fatal("Missing privilege separation directory: %s", - _PATH_PRIVSEP_CHROOT_DIR); - if (st.st_uid != 0 || (st.st_mode & (S_IWGRP|S_IWOTH)) != 0) - fatal("%s must be owned by root and not group or " - "world-writable.", _PATH_PRIVSEP_CHROOT_DIR); - } + /* Ensure privsep directory is correctly configured. */ + if (getpwnam(SSH_PRIVSEP_USER) == NULL) + fatal("Privilege separation user %s does not exist", + SSH_PRIVSEP_USER); + endpwent(); + if ((stat(_PATH_PRIVSEP_CHROOT_DIR, &sb) == -1) || + (S_ISDIR(sb.st_mode) == 0)) + fatal("Missing privilege separation directory: %s", + _PATH_PRIVSEP_CHROOT_DIR); + if (sb.st_uid != 0 || (sb.st_mode & (S_IWGRP|S_IWOTH)) != 0) + fatal("%s must be owned by root and not group or " + "world-writable.", _PATH_PRIVSEP_CHROOT_DIR); if (test_flag > 1) - print_config(ssh, connection_info); + print_config(&connection_info); /* Configuration looks good, so exit if in test mode. */ if (test_flag) exit(0); - if (rexec_flag) { - if (rexec_argc < 0) - fatal("rexec_argc %d < 0", rexec_argc); - rexec_argv = xcalloc(rexec_argc + 2, sizeof(char *)); - for (i = 0; i < (u_int)rexec_argc; i++) { - debug("rexec_argv[%d]='%s'", i, saved_argv[i]); - rexec_argv[i] = saved_argv[i]; - } - rexec_argv[rexec_argc] = "-R"; - rexec_argv[rexec_argc + 1] = NULL; + /* Prepare arguments for sshd-session */ + if (rexec_argc < 0) + fatal("rexec_argc %d < 0", rexec_argc); + rexec_argv = xcalloc(rexec_argc + 3, sizeof(char *)); + /* Point to the sshd-session binary instead of sshd */ + rexec_argv[0] = options.sshd_session_path; + for (i = 1; i < (u_int)rexec_argc; i++) { + debug("rexec_argv[%d]='%s'", i, saved_argv[i]); + rexec_argv[i] = saved_argv[i]; } + rexec_argv[rexec_argc++] = "-R"; + rexec_argv[rexec_argc] = NULL; + if (stat(rexec_argv[0], &sb) != 0 || !(sb.st_mode & (S_IXOTH|S_IXUSR))) + fatal("%s does not exist or is not executable", rexec_argv[0]); + debug3("using %s for re-exec", rexec_argv[0]); + listener_proctitle = prepare_proctitle(ac, av); /* Ensure that umask disallows at least group and world write */ @@ -1903,7 +1284,7 @@ main(int ac, char **av) (void) umask(new_umask); /* Initialize the log (it is reinitialized below in case we forked). */ - if (debug_flag && (!inetd_flag || rexeced_flag)) + if (debug_flag && !inetd_flag) log_stderr = 1; log_init(__progname, options.log_level, options.log_facility, log_stderr); @@ -1938,7 +1319,11 @@ main(int ac, char **av) /* Get a connection, either from inetd or a listening TCP socket */ if (inetd_flag) { - server_accept_inetd(&sock_in, &sock_out); + /* Send configuration to ancestor sshd-session process */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, config_s) == -1) + fatal("socketpair: %s", strerror(errno)); + send_rexec_state(config_s[0], cfg); + close(config_s[0]); } else { server_listen(); @@ -1965,7 +1350,7 @@ main(int ac, char **av) /* Accept a connection and return in a forked child */ server_accept_loop(&sock_in, &sock_out, - &newsock, config_s); + &newsock, config_s, log_stderr); } /* This is the child processing a new connection. */ @@ -1979,316 +1364,36 @@ main(int ac, char **av) if (!debug_flag && !inetd_flag && setsid() == -1) error("setsid: %.100s", strerror(errno)); - if (rexec_flag) { - debug("rexec start in %d out %d newsock %d pipe %d sock %d", - sock_in, sock_out, newsock, startup_pipe, config_s[0]); + debug("rexec start in %d out %d newsock %d pipe %d sock %d/%d", + sock_in, sock_out, newsock, startup_pipe, config_s[0], config_s[1]); + if (!inetd_flag) { if (dup2(newsock, STDIN_FILENO) == -1) - debug3_f("dup2 stdin: %s", strerror(errno)); + debug3("dup2 stdin: %s", strerror(errno)); if (dup2(STDIN_FILENO, STDOUT_FILENO) == -1) - debug3_f("dup2 stdout: %s", strerror(errno)); - if (startup_pipe == -1) - close(REEXEC_STARTUP_PIPE_FD); - else if (startup_pipe != REEXEC_STARTUP_PIPE_FD) { - if (dup2(startup_pipe, REEXEC_STARTUP_PIPE_FD) == -1) - debug3_f("dup2 startup_p: %s", strerror(errno)); - close(startup_pipe); - startup_pipe = REEXEC_STARTUP_PIPE_FD; - } - + debug3("dup2 stdout: %s", strerror(errno)); + } + if (config_s[1] != REEXEC_CONFIG_PASS_FD) { if (dup2(config_s[1], REEXEC_CONFIG_PASS_FD) == -1) - debug3_f("dup2 config_s: %s", strerror(errno)); + debug3("dup2 config_s: %s", strerror(errno)); close(config_s[1]); - - ssh_signal(SIGHUP, SIG_IGN); /* avoid reset to SIG_DFL */ - execv(rexec_argv[0], rexec_argv); - - /* Reexec has failed, fall back and continue */ - error("rexec of %s failed: %s", rexec_argv[0], strerror(errno)); - recv_rexec_state(REEXEC_CONFIG_PASS_FD, NULL); - log_init(__progname, options.log_level, - options.log_facility, log_stderr); - - /* Clean up fds */ - close(REEXEC_CONFIG_PASS_FD); - newsock = sock_out = sock_in = dup(STDIN_FILENO); - if (stdfd_devnull(1, 1, 0) == -1) - error_f("stdfd_devnull failed"); - debug("rexec cleanup in %d out %d newsock %d pipe %d sock %d", - sock_in, sock_out, newsock, startup_pipe, config_s[0]); - } - - /* 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); - - /* - * 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); - process_channel_timeouts(ssh, &options); - process_permitopen(ssh, &options); - - /* 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); - } - - /* - * 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); - - 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) - alarm(options.login_grace_time); - - if ((r = kex_exchange_identification(ssh, -1, - options.version_addendum)) != 0) - 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_f("sshbuf_new failed"); - auth_debug_reset(); - - if (use_privsep) { - if (privsep_preauth(ssh) == 1) - goto authenticated; - } else if (have_agent) { - if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { - error_r(r, "Unable to get agent socket"); - have_agent = 0; - } } - - /* perform the key exchange */ - /* authenticate user and start session */ - do_ssh2_kex(ssh); - do_authentication2(ssh); - - /* - * If we use privilege separation, the unprivileged child transfers - * the current keystate and exits - */ - if (use_privsep) { - mm_send_keystate(ssh, pmonitor); - ssh_packet_clear_keys(ssh); - exit(0); - } - - authenticated: - /* - * Cancel the alarm we set to limit the time taken for - * authentication. - */ - alarm(0); - ssh_signal(SIGALRM, SIG_DFL); - authctxt->authenticated = 1; - if (startup_pipe != -1) { + if (startup_pipe == -1) + close(REEXEC_STARTUP_PIPE_FD); + else if (startup_pipe != REEXEC_STARTUP_PIPE_FD) { + if (dup2(startup_pipe, REEXEC_STARTUP_PIPE_FD) == -1) + debug3("dup2 startup_p: %s", strerror(errno)); close(startup_pipe); - startup_pipe = -1; - } - - if (options.routing_domain != NULL) - set_process_rdomain(ssh, options.routing_domain); - - /* - * In privilege separation, we fork another child and prepare - * file descriptor passing. - */ - if (use_privsep) { - 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); - ssh_packet_close(ssh); - - if (use_privsep) - 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) -{ - int r; - - if (use_privsep) { - 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"); - } - } else { - if (privkey) { - if (sshkey_sign(privkey, signature, slenp, data, dlen, - alg, options.sk_provider, NULL, ssh->compat) < 0) - fatal_f("privkey sign failed"); - } else { - if ((r = ssh_agent_sign(auth_sock, pubkey, - signature, slenp, data, dlen, alg, - ssh->compat)) != 0) { - fatal_fr(r, "agent sign failed"); - } - } } - return 0; -} - -/* SSH2 key exchange */ -static void -do_ssh2_kex(struct ssh *ssh) -{ - char *hkalgs = NULL, *myproposal[PROPOSAL_MAX]; - const char *compression = NULL; - struct kex *kex; - int r; - if (options.rekey_limit || options.rekey_interval) - ssh_packet_set_rekey_limits(ssh, options.rekey_limit, - options.rekey_interval); + ssh_signal(SIGHUP, SIG_IGN); /* avoid reset to SIG_DFL */ + execv(rexec_argv[0], rexec_argv); - if (options.compression == COMP_NONE) - compression = "none"; - hkalgs = list_hostkey_types(); - - kex_proposal_populate_entries(ssh, myproposal, options.kex_algorithms, - options.ciphers, options.macs, compression, hkalgs); - - free(hkalgs); - - /* start key exchange */ - if ((r = kex_setup(ssh, myproposal)) != 0) - fatal_r(r, "kex_setup"); - kex_set_server_sig_algs(ssh, options.pubkey_accepted_algos); - kex = ssh->kex; - -#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; - kex->kex[KEX_ECDH_SHA2] = kex_gen_server; -#endif - kex->kex[KEX_C25519_SHA256] = kex_gen_server; - kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = 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; - - ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &kex->done); - kex_proposal_free_entries(myproposal); - -#ifdef DEBUG_KEXDH - /* send 1st encrypted/maced/compressed message */ - if ((r = sshpkt_start(ssh, SSH2_MSG_IGNORE)) != 0 || - (r = sshpkt_put_cstring(ssh, "markus")) != 0 || - (r = sshpkt_send(ssh)) != 0 || - (r = ssh_packet_write_wait(ssh)) != 0) - fatal_fr(r, "send test"); -#endif - debug("KEX done"); + fatal("rexec of %s failed: %s", rexec_argv[0], strerror(errno)); } /* server specific fatal cleanup */ void cleanup_exit(int i) { - if (the_active_state != NULL && the_authctxt != NULL) { - do_cleanup(the_active_state, the_authctxt); - if (use_privsep && 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)); - } - } - } _exit(i); } diff --git a/usr.bin/ssh/sshd/Makefile b/usr.bin/ssh/sshd/Makefile index 77a0b8d0ccd..65434360a3d 100644 --- a/usr.bin/ssh/sshd/Makefile +++ b/usr.bin/ssh/sshd/Makefile @@ -1,17 +1,11 @@ -# $OpenBSD: Makefile,v 1.110 2024/04/01 15:48:16 deraadt Exp $ +# $OpenBSD: Makefile,v 1.111 2024/05/17 00:30:24 djm Exp $ .PATH: ${.CURDIR}/.. -SRCS= sshd.c auth-rhosts.c auth-passwd.c sshpty.c sshlogin.c servconf.c \ - serverloop.c auth.c auth2.c auth-options.c session.c auth2-chall.c \ - groupaccess.c auth-bsdauth.c auth2-hostbased.c auth2-kbdint.c \ - auth2-none.c auth2-passwd.c auth2-pubkey.c auth2-pubkeyfile.c \ - monitor.c monitor_wrap.c \ - sftp-server.c sftp-common.c sftp-realpath.c sandbox-pledge.c srclimit.c -SRCS+= authfd.c compat.c dns.c fatal.c hostfile.c readpass.c utf8.c uidswap.c -SRCS+= ${SRCS_BASE} ${SRCS_KEX} ${SRCS_KEXS} ${SRCS_KEY} ${SRCS_KEYP} \ - ${SRCS_KRL} ${SRCS_PROT} ${SRCS_PKT} ${SRCS_UTL} ${SRCS_PKCS11} \ - ${SRCS_SK_CLIENT} +SRCS= sshd.c servconf.c sshpty.c srclimit.c groupaccess.c auth2-methods.c +SRCS+= dns.c fatal.c compat.c utf8.c authfd.c canohost.c kex-names.c +SRCS+= ${SRCS_BASE} ${SRCS_KEY} ${SRCS_KEYP} ${SRCS_KRL} \ + ${SRCS_MAC} ${SRCS_UTL} ${SRCS_SK_CLIENT} PROG= sshd BINMODE=511 @@ -50,7 +44,7 @@ DPADD+= ${LIBZ} # The random relink kit, used on OpenBSD by /etc/rc -CLEANFILES+= sshd.tar install.sh +CLEANFILES+= ${PROG}.tar install.sh install.sh: Makefile echo "set -o errexit" > $@ -- 2.20.1