--- hpn-ssh-hpn-18.4.2/auth-pam.c.orig +++ hpn-ssh-hpn-18.4.2/auth-pam.c @@ -689,6 +689,66 @@ sshpam_handle = NULL; } +#ifdef PAM_ENHANCEMENT +char * +derive_pam_service_name(Authctxt *authctxt) +{ + char *svcname = xmalloc(BUFSIZ); + + /* + * If PamServiceName is set we use that for everything, including + * SSHv1 + */ + if (options.pam_service_name != NULL) { + (void) strlcpy(svcname, options.pam_service_name, BUFSIZ); + return (svcname); + } + + char *method_name = authctxt->authmethod_name; + + if (!method_name) + fatal("Userauth method unknown while starting PAM"); + + /* + * For SSHv2 we use "sshd- + * The "sshd" prefix can be changed via the PAMServicePrefix + * sshd_config option. + */ + if (strcmp(method_name, "none") == 0) { + snprintf(svcname, BUFSIZ, "%s-none", + options.pam_service_prefix); + } + if (strcmp(method_name, "password") == 0) { + snprintf(svcname, BUFSIZ, "%s-password", + options.pam_service_prefix); + } + if (strcmp(method_name, "keyboard-interactive") == 0) { + /* "keyboard-interactive" is too long, shorten it */ + snprintf(svcname, BUFSIZ, "%s-kbdint", + options.pam_service_prefix); + } + if (strcmp(method_name, "publickey") == 0) { + /* "publickey" is too long, shorten it */ + snprintf(svcname, BUFSIZ, "%s-pubkey", + options.pam_service_prefix); + } + if (strcmp(method_name, "hostbased") == 0) { + snprintf(svcname, BUFSIZ, "%s-hostbased", + options.pam_service_prefix); + } + if (strncmp(method_name, "gssapi-", 7) == 0) { + /* + * Although OpenSSH only supports "gssapi-with-mic" + * for now. We will still map any userauth method + * prefixed with "gssapi-" to the gssapi PAM service. + */ + snprintf(svcname, BUFSIZ, "%s-gssapi", + options.pam_service_prefix); + } + return svcname; +} +#endif /* PAM_ENHANCEMENT */ + static int sshpam_init(struct ssh *ssh, Authctxt *authctxt) { @@ -702,23 +762,76 @@ fatal("Username too long from %s port %d", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); #endif + +#ifdef PAM_ENHANCEMENT + const char *pam_service; + const char **ptr_pam_service = &pam_service; + char *svc = NULL; + + svc = derive_pam_service_name(authctxt); + debug3("PAM service is %s", svc); +#endif + if (sshpam_handle == NULL) { if (ssh == NULL) { fatal("%s: called initially with no " "packet context", __func__); } } if (sshpam_handle != NULL) { +#ifdef PAM_ENHANCEMENT + /* get the pam service name */ + sshpam_err = pam_get_item(sshpam_handle, + PAM_SERVICE, (sshpam_const void **)ptr_pam_service); + if (sshpam_err != PAM_SUCCESS) + fatal("Failed to get the PAM service name"); + debug3("Previous pam_service is %s", pam_service ? + pam_service : "NULL"); + + /* get the pam user name */ + sshpam_err = pam_get_item(sshpam_handle, + PAM_USER, (sshpam_const void **)ptr_pam_user); + + /* + * only need to re-start if either user or service is + * different. + */ + if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0 + && strncmp(svc, pam_service, strlen(svc)) == 0) { + free(svc); + return (0); + } + + /* + * Clean up previous PAM state. No need to clean up session + * and creds. + */ + sshpam_authenticated = 0; + sshpam_account_status = -1; + + sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, NULL); + if (sshpam_err != PAM_SUCCESS) + debug3("Cannot remove PAM conv"); /* a warning only */ +#else /* Original */ /* We already have a PAM context; check if the user matches */ sshpam_err = pam_get_item(sshpam_handle, PAM_USER, (sshpam_const void **)ptr_pam_user); if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0) return (0); +#endif /* PAM_ENHANCEMENT */ pam_end(sshpam_handle, sshpam_err); sshpam_handle = NULL; } debug("PAM: initializing for \"%s\"", user); +#ifdef PAM_ENHANCEMENT + debug3("Starting PAM service %s for user %s method %s", svc, user, + authctxt->authmethod_name); + sshpam_err = + pam_start(svc, user, &store_conv, &sshpam_handle); + free(svc); +#else /* Original */ sshpam_err = pam_start(SSHD_PAM_SERVICE, user, &store_conv, &sshpam_handle); +#endif sshpam_authctxt = authctxt; if (sshpam_err != PAM_SUCCESS) { --- hpn-ssh-hpn-18.4.2/auth.h.orig +++ hpn-ssh-hpn-18.4.2/auth.h @@ -95,6 +95,9 @@ /* Information exposed to session */ struct sshbuf *session_info; /* Auth info for environment */ +#ifdef PAM_ENHANCEMENT + char *authmethod_name; +#endif }; /* --- hpn-ssh-hpn-18.4.2/auth2.c.orig +++ hpn-ssh-hpn-18.4.2/auth2.c @@ -317,9 +317,17 @@ #endif } #ifdef USE_PAM +#ifdef PAM_ENHANCEMENT + /* + * Start PAM here and once only, if each userauth does not + * has its own PAM service. + */ + if (options.use_pam && !options.pam_service_per_authmethod) +#else if (options.use_pam) +#endif /* PAM_ENHANCEMENT */ PRIVSEP(start_pam(ssh)); -#endif +#endif /* USE_PAM */ ssh_packet_set_log_preamble(ssh, "%suser %s", authctxt->valid ? "authenticating " : "invalid ", user); setproctitle("%s%s", authctxt->valid ? user : "unknown", @@ -356,6 +364,18 @@ /* try to authenticate user */ m = authmethod_lookup(authctxt, method); if (m != NULL && authctxt->failures < options.max_authtries) { + +#if defined(USE_PAM) && defined(PAM_ENHANCEMENT) + /* start PAM service for each userauth */ + if (options.use_pam && options.pam_service_per_authmethod) { + if (authctxt->authmethod_name != NULL) + free(authctxt->authmethod_name); + authctxt->authmethod_name = xstrdup(method); + if (use_privsep) + mm_inform_authmethod(method); + PRIVSEP(start_pam(authctxt)); + } +#endif debug2("input_userauth_request: try method %s", method); authenticated = m->userauth(ssh, method); } @@ -381,6 +401,10 @@ char *methods; int r, partial = 0; +#ifdef PAM_ENHANCEMENT + debug3("%s: entering", __func__); +#endif + if (authenticated) { if (!authctxt->valid) { fatal("INTERNAL ERROR: authenticated invalid user %s", @@ -404,6 +428,25 @@ } if (authenticated && options.num_auth_methods != 0) { + +#if defined(USE_PAM) && defined(PAM_ENHANCEMENT) + /* + * If each userauth has its own PAM service, then PAM needs to + * perform the account check for this service. + */ + if (options.use_pam && options.pam_service_per_authmethod && + !PRIVSEP(do_pam_account())) { + /* if PAM returned a message, send it to the user */ + if (sshbuf_len(loginmsg) > 0) { + sshbuf_put(loginmsg, "\0", 1); + userauth_send_banner(ssh, sshbuf_ptr(loginmsg)); + ssh_packet_write_wait(ssh); + } + + fatal("Access denied for user %s by PAM account " + "configuration", authctxt->user); + } +#endif if (!auth2_update_methods_lists(authctxt, method, submethod)) { authenticated = 0; partial = 1; @@ -421,7 +464,19 @@ return; #ifdef USE_PAM +#ifdef PAM_ENHANCEMENT + /* + * PAM needs to perform account checks after auth. However, if each + * userauth has its own PAM service and options.num_auth_methods != 0, + * then no need to perform account checking, because it was done + * already. + */ + if (options.use_pam && authenticated && + !(options.num_auth_methods != 0 && + options.pam_service_per_authmethod)) { +#else if (options.use_pam && authenticated) { +#endif int r, success = PRIVSEP(do_pam_account()); /* If PAM returned a message, send it to the user. */ --- hpn-ssh-hpn-18.4.2/monitor.c.orig +++ hpn-ssh-hpn-18.4.2/monitor.c @@ -117,6 +117,9 @@ int mm_answer_pwnamallow(struct ssh *, int, struct sshbuf *); int mm_answer_auth2_read_banner(struct ssh *, int, struct sshbuf *); int mm_answer_authserv(struct ssh *, int, struct sshbuf *); +#ifdef PAM_ENHANCEMENT +int mm_answer_authmethod(struct ssh *, int, struct sshbuf *); +#endif int mm_answer_authpassword(struct ssh *, int, struct sshbuf *); int mm_answer_bsdauthquery(struct ssh *, int, struct sshbuf *); int mm_answer_bsdauthrespond(struct ssh *, int, struct sshbuf *); @@ -190,10 +193,17 @@ {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign}, {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow}, {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv}, +#ifdef PAM_ENHANCEMENT + {MONITOR_REQ_AUTHMETHOD, MON_ISAUTH, mm_answer_authmethod}, +#endif {MONITOR_REQ_AUTH2_READ_BANNER, MON_ONCE, mm_answer_auth2_read_banner}, {MONITOR_REQ_AUTHPASSWORD, MON_AUTH, mm_answer_authpassword}, #ifdef USE_PAM +#ifdef PAM_ENHANCEMENT + {MONITOR_REQ_PAM_START, MON_ISAUTH, mm_answer_pam_start}, +#else {MONITOR_REQ_PAM_START, MON_ONCE, mm_answer_pam_start}, +#endif {MONITOR_REQ_PAM_ACCOUNT, 0, mm_answer_pam_account}, {MONITOR_REQ_PAM_INIT_CTX, MON_ONCE, mm_answer_pam_init_ctx}, {MONITOR_REQ_PAM_QUERY, 0, mm_answer_pam_query}, @@ -300,6 +310,25 @@ /* Special handling for multiple required authentications */ if (options.num_auth_methods != 0) { + +#if defined(USE_PAM) && defined(PAM_ENHANCEMENT) + /* + * If each userauth has its own PAM service, then PAM + * need to perform account check for this service. + */ + if (options.use_pam && authenticated && + options.pam_service_per_authmethod) { + struct sshbuf *m; + if ((m = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new", __func__); + mm_request_receive_expect(pmonitor->m_sendfd, + MONITOR_REQ_PAM_ACCOUNT, m); + authenticated = mm_answer_pam_account(ssh, + pmonitor->m_sendfd, m); + sshbuf_free(m); + } +#endif + if (authenticated && !auth2_update_methods_lists(authctxt, auth_method, auth_submethod)) { @@ -317,8 +346,21 @@ !auth_root_allowed(ssh, auth_method)) authenticated = 0; #ifdef USE_PAM +#ifdef PAM_ENHANCEMENT + /* + * PAM needs to perform account checks after auth. + * However, if each userauth has its own PAM service + * and options.num_auth_methods != 0, then no need to + * perform account checking, because it was done + * already. + */ + if (options.use_pam && authenticated && + !(options.num_auth_methods != 0 && + options.pam_service_per_authmethod)) { +#else /* PAM needs to perform account checks after auth */ if (options.use_pam && authenticated) { +#endif struct sshbuf *m; if ((m = sshbuf_new()) == NULL) @@ -802,6 +844,11 @@ monitor_permit(mon_dispatch, MONITOR_REQ_AUTHSERV, 1); monitor_permit(mon_dispatch, MONITOR_REQ_AUTH2_READ_BANNER, 1); +#ifdef PAM_ENHANCEMENT + /* Allow authmethod information on the auth context */ + monitor_permit(mon_dispatch, MONITOR_REQ_AUTHMETHOD, 1); +#endif + #ifdef USE_PAM if (options.use_pam) monitor_permit(mon_dispatch, MONITOR_REQ_PAM_START, 1); @@ -825,6 +872,27 @@ return (0); } +#ifdef PAM_ENHANCEMENT +int +mm_answer_authmethod(struct ssh *ssh, int sock, struct sshbuf *m) +{ + Authctxt *authctxt = ssh->authctxt; + + monitor_permit_authentications(1); + /*sshbuf_dump(m, stderr);*/ + sshbuf_get_cstring(m, &authctxt->authmethod_name, NULL); + debug3("%s: authmethod_name=%s", __func__, authctxt->authmethod_name); + + if (authctxt->authmethod_name && + strlen(authctxt->authmethod_name) == 0) { + free(authctxt->authmethod_name); + authctxt->authmethod_name = NULL; + } + + return (0); +} +#endif + int mm_answer_authserv(struct ssh *ssh, int sock, struct sshbuf *m) { --- hpn-ssh-hpn-18.4.2/monitor.h.orig +++ hpn-ssh-hpn-18.4.2/monitor.h @@ -63,6 +63,9 @@ MONITOR_REQ_PAM_FREE_CTX = 110, MONITOR_ANS_PAM_FREE_CTX = 111, MONITOR_REQ_AUDIT_EVENT = 112, MONITOR_REQ_AUDIT_COMMAND = 113, +#ifdef PAM_ENHANCEMENT + MONITOR_REQ_AUTHMETHOD = 114, +#endif }; struct ssh; --- hpn-ssh-hpn-18.4.2/monitor_wrap.c.orig +++ hpn-ssh-hpn-18.4.2/monitor_wrap.c @@ -396,6 +396,24 @@ sshbuf_free(m); } +#ifdef PAM_ENHANCEMENT +/* Inform the privileged process about the authentication method */ +void +mm_inform_authmethod(char *authmethod) +{ + struct sshbuf *m; + + debug3("%s entering", __func__); + if ((m = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new", __func__); + sshbuf_put_cstring(m, authmethod); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHMETHOD, m); + + sshbuf_free(m); +} +#endif + /* Do the password authentication */ int mm_auth_password(struct ssh *ssh, char *password) --- hpn-ssh-hpn-18.4.2/servconf.c.orig +++ hpn-ssh-hpn-18.4.2/servconf.c @@ -202,6 +202,18 @@ options->channel_timeouts = NULL; options->num_channel_timeouts = 0; options->unused_connection_timeout = -1; +#ifdef PAM_ENHANCEMENT + options->pam_service_name = NULL; + options->pam_service_prefix = NULL; + + /* + * Each user method will have its own PAM service by default. + * However, if PAMServiceName is specified + * then there will be only one PAM service for the + * entire user authentication. + */ + options->pam_service_per_authmethod = 1; +#endif } /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ @@ -451,6 +463,12 @@ options->ip_qos_bulk = IPTOS_DSCP_CS1; if (options->version_addendum == NULL) options->version_addendum = xstrdup(""); + +#ifdef PAM_ENHANCEMENT + if (options->pam_service_prefix == NULL) + options->pam_service_prefix = _SSH_PAM_SERVICE_PREFIX; +#endif + if (options->fwd_opts.streamlocal_bind_mask == (mode_t)-1) options->fwd_opts.streamlocal_bind_mask = 0177; if (options->fwd_opts.streamlocal_bind_unlink == -1) @@ -543,6 +561,9 @@ sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory, sUsePrivilegeSeparation, sAllowAgentForwarding, sHostCertificate, sInclude, +#ifdef PAM_ENHANCEMENT + sPAMServicePrefix, sPAMServiceName, +#endif sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile, sAuthorizedPrincipalsCommand, sAuthorizedPrincipalsCommandUser, sKexAlgorithms, sCASignatureAlgorithms, sIPQoS, sVersionAddendum, @@ -694,6 +715,10 @@ { "forcecommand", sForceCommand, SSHCFG_ALL }, { "chrootdirectory", sChrootDirectory, SSHCFG_ALL }, { "hostcertificate", sHostCertificate, SSHCFG_GLOBAL }, +#ifdef PAM_ENHANCEMENT + { "pamserviceprefix", sPAMServicePrefix, SSHCFG_GLOBAL }, + { "pamservicename", sPAMServiceName, SSHCFG_GLOBAL }, +#endif { "revokedkeys", sRevokedKeys, SSHCFG_ALL }, { "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL }, { "authorizedprincipalsfile", sAuthorizedPrincipalsFile, SSHCFG_ALL }, @@ -2649,6 +2674,37 @@ } goto parse_time; + case sPAMServicePrefix: + arg = strdelim(&str); + if (!arg || *arg == '\0') + fatal("%s line %d: Missing argument.", + filename, linenum); + if (options->pam_service_name != NULL) + fatal("%s line %d: PAMServiceName and PAMServicePrefix" + " are mutually exclusive.", filename, linenum); + if (options->pam_service_prefix == NULL) + options->pam_service_prefix = xstrdup(arg); + break; + + case sPAMServiceName: + arg = strdelim(&str); + if (!arg || *arg == '\0') + fatal("%s line %d: Missing argument.", + filename, linenum); + if (options->pam_service_prefix != NULL) + fatal("%s line %d: PAMServiceName and PAMServicePrefix" + " are mutually exclusive.", filename, linenum); + if (options->pam_service_name == NULL) { + options->pam_service_name = xstrdup(arg); + + /* + * When this option is specified, we will not have + * PAM service for each auth method. + */ + options->pam_service_per_authmethod = 0; + } + break; + case sDeprecated: case sIgnore: case sUnsupported: --- hpn-ssh-hpn-18.4.2/servconf.h.orig +++ hpn-ssh-hpn-18.4.2/servconf.h @@ -71,6 +71,10 @@ struct addrinfo *addrs; }; +#ifdef PAM_ENHANCEMENT +#define _SSH_PAM_SERVICE_PREFIX "sshd" +#endif + typedef struct { u_int num_ports; u_int ports_from_cmdline; @@ -227,6 +231,12 @@ u_int num_auth_methods; char **auth_methods; +#ifdef PAM_ENHANCEMENT + char *pam_service_prefix; + char *pam_service_name; + int pam_service_per_authmethod; +#endif + int fingerprint_hash; int expose_userauth_info; u_int64_t timing_secret; --- hpn-ssh-hpn-18.4.2/hpnsshd.8.orig +++ hpn-ssh-hpn-18.4.2/hpnsshd.8 @@ -1017,6 +1017,33 @@ started last). The content of this file is not sensitive; it can be world-readable. .El + +.Sh SECURITY +sshd uses pam(3PAM) for password and keyboard-interactive methods as well as +for account management, session management, and the password management for all +authentication methods. +.Pp +Each SSHv2 userauth type has its own PAM service name: + +.Bd -literal -offset 3n + +----------------------------------------------- +| SSHv2 Userauth | PAM Service Name | +----------------------------------------------- +| none | sshd-none | +----------------------------------------------- +| password | sshd-password | +----------------------------------------------- +| keyboard-interactive | sshd-kbdint | +----------------------------------------------- +| pubkey | sshd-pubkey | +----------------------------------------------- +| hostbased | sshd-hostbased | +----------------------------------------------- +| gssapi-with-mic | sshd-gssapi | +----------------------------------------------- +.Ed + .Sh SEE ALSO .Xr hpnscp 1 , .Xr hpnsftp 1 , --- hpn-ssh-hpn-18.4.2/hpnsshd_config.5.orig +++ hpn-ssh-hpn-18.4.2/hpnsshd_config.5 @@ -1400,6 +1400,35 @@ protection against man-in-the-middle attacks. As with NoneEnabled all authentication remains encrypted and integrity is ensured. Default is .Cm no. +.It Cm PAMServiceName +Specifies the PAM service name for the PAM session. +The +.Cm PAMServiceName +and +.Cm PAMServicePrefix +options are mutually exclusive and if both are set, sshd does not start. +If this option is set the service name is the same for all user authentication +methods. +The option has no default value. +See +.Cm PAMServicePrefix +for more information. +.It Cm PAMServicePrefix +Specifies the PAM service name prefix for service names used for individual +user authentication methods. +The default is sshd. +The +.Cm PAMServiceName +and +.Cm PAMServicePrefix +options are mutually exclusive and if both are set, sshd does not start. +.Pp +For example, if this option is set to +.Cm admincli , +the service name for the keyboard-interactive authentication method is +.Sy admincli-kbdint +instead of the default +.Sy sshd-kbdint . .It Cm PasswordAuthentication Specifies whether password authentication is allowed. The default is @@ -1950,8 +1979,7 @@ is enabled, you will not be able to run .Xr sshd 8 as a non-root user. -The default is -.Cm no . +On OpenIndiana, the option is always enabled. .It Cm VersionAddendum Optionally specifies additional text to append to the SSH protocol banner sent by the server upon connection.