diff -wpruN --no-dereference '--exclude=*.orig' a~/auth-pam.c a/auth-pam.c --- a~/auth-pam.c 1970-01-01 00:00:00 +++ a/auth-pam.c 1970-01-01 00:00:00 @@ -683,6 +683,66 @@ sshpam_cleanup(void) 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) { @@ -690,8 +750,17 @@ sshpam_init(struct ssh *ssh, Authctxt *a const char **ptr_pam_user = &pam_user; int r; +#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); +#else if (options.pam_service_name == NULL) fatal_f("internal error: NULL PAM service name"); +#endif #if defined(PAM_SUN_CODEBASE) && defined(PAM_MAX_RESP_SIZE) /* Protect buggy PAM implementations from excessively long usernames */ if (strlen(user) >= PAM_MAX_RESP_SIZE) @@ -705,18 +774,62 @@ sshpam_init(struct ssh *ssh, Authctxt *a } } 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 /* 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_end(sshpam_handle, sshpam_err); sshpam_handle = NULL; } +#ifdef PAM_ENHANCEMENT + debug("PAM: initializing for \"%s\" with service \"%s\"", user, svc); + 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 debug("PAM: initializing for \"%s\" with service \"%s\"", user, options.pam_service_name); sshpam_err = pam_start(options.pam_service_name, user, &store_conv, &sshpam_handle); +#endif sshpam_authctxt = authctxt; if (sshpam_err != PAM_SUCCESS) { diff -wpruN --no-dereference '--exclude=*.orig' a~/auth.h a/auth.h --- a~/auth.h 1970-01-01 00:00:00 +++ a/auth.h 1970-01-01 00:00:00 @@ -95,6 +95,9 @@ struct Authctxt { /* Information exposed to session */ struct sshbuf *session_info; /* Auth info for environment */ +#ifdef PAM_ENHANCEMENT + char *authmethod_name; +#endif }; /* diff -wpruN --no-dereference '--exclude=*.orig' a~/auth2.c a/auth2.c --- a~/auth2.c 1970-01-01 00:00:00 +++ a/auth2.c 1970-01-01 00:00:00 @@ -305,9 +305,17 @@ input_userauth_request(int type, u_int32 #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 */ mm_start_pam(ssh); -#endif +#endif /* USE_PAM */ ssh_packet_set_log_preamble(ssh, "%suser %s", authctxt->valid ? "authenticating " : "invalid ", user); setproctitle("%s [net]", authctxt->valid ? user : "unknown"); @@ -342,6 +350,17 @@ input_userauth_request(int type, u_int32 /* 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) { + extern void mm_inform_authmethod(char *); + if (authctxt->authmethod_name != NULL) + free(authctxt->authmethod_name); + authctxt->authmethod_name = xstrdup(method); + mm_inform_authmethod(method); + mm_start_pam(ssh); + } +#endif debug2("input_userauth_request: try method %s", method); authenticated = m->userauth(ssh, method); } @@ -367,6 +386,10 @@ userauth_finish(struct ssh *ssh, int aut 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", @@ -390,6 +413,31 @@ userauth_finish(struct ssh *ssh, int aut } 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) { + int r, success = mm_do_pam_account(); + + /* If PAM returned a message, send it to the user. */ + if (sshbuf_len(loginmsg) > 0) { + if ((r = sshbuf_put(loginmsg, "\0", 1)) != 0) + fatal("%s: buffer error: %s", + __func__, ssh_err(r)); + userauth_send_banner(ssh, sshbuf_ptr(loginmsg)); + if ((r = ssh_packet_write_wait(ssh)) != 0) { + sshpkt_fatal(ssh, r, + "%s: send PAM banner", __func__); + } + } + if (!success) { + fatal("Access denied for user %s by PAM " + "account configuration", authctxt->user); + } + } +#endif if (!auth2_update_methods_lists(authctxt, method, submethod)) { authenticated = 0; partial = 1; @@ -407,7 +455,19 @@ userauth_finish(struct ssh *ssh, int aut 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 /* PAM_ENHANCEMENT */ int r, success = mm_do_pam_account(); /* If PAM returned a message, send it to the user. */ diff -wpruN --no-dereference '--exclude=*.orig' a~/monitor.c a/monitor.c --- a~/monitor.c 1970-01-01 00:00:00 +++ a/monitor.c 1970-01-01 00:00:00 @@ -118,6 +118,9 @@ int mm_answer_sign(struct ssh *, int, st 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 @@ struct mon_table mon_dispatch_proto20[] {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}, @@ -304,6 +314,23 @@ monitor_child_preauth(struct ssh *ssh, s /* 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)) { @@ -321,8 +348,21 @@ monitor_child_preauth(struct ssh *ssh, s !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) @@ -826,6 +866,10 @@ mm_answer_pwnamallow(struct ssh *ssh, in /* Allow service/style information on the auth context */ monitor_permit(mon_dispatch, MONITOR_REQ_AUTHSERV, 1); monitor_permit(mon_dispatch, MONITOR_REQ_AUTH2_READ_BANNER, 1); +#ifdef 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) @@ -850,6 +894,27 @@ int mm_answer_auth2_read_banner(struct s 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) { diff -wpruN --no-dereference '--exclude=*.orig' a~/monitor.h a/monitor.h --- a~/monitor.h 1970-01-01 00:00:00 +++ a/monitor.h 1970-01-01 00:00:00 @@ -62,6 +62,9 @@ enum monitor_reqtype { MONITOR_REQ_PAM_RESPOND = 108, MONITOR_ANS_PAM_RESPOND = 109, 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 }; diff -wpruN --no-dereference '--exclude=*.orig' a~/monitor_wrap.c a/monitor_wrap.c --- a~/monitor_wrap.c 1970-01-01 00:00:00 +++ a/monitor_wrap.c 1970-01-01 00:00:00 @@ -452,6 +452,24 @@ mm_inform_authserv(char *service, char * 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 /* PAM_ENHANCEMENT */ + /* Do the password authentication */ int mm_auth_password(struct ssh *ssh, char *password) diff -wpruN --no-dereference '--exclude=*.orig' a~/servconf.c a/servconf.c --- a~/servconf.c 1970-01-01 00:00:00 +++ a/servconf.c 1970-01-01 00:00:00 @@ -93,6 +93,17 @@ initialize_server_options(ServerOptions /* Portable-specific options */ options->use_pam = -1; options->pam_service_name = NULL; +#ifdef PAM_ENHANCEMENT + 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 /* Standard Options */ options->num_ports = 0; @@ -303,8 +314,13 @@ fill_default_server_options(ServerOption #else options->use_pam = 0; #endif +#ifdef PAM_ENHANCEMENT + if (options->pam_service_prefix == NULL) + options->pam_service_prefix = xstrdup(SSHD_PAM_SERVICE); +#else if (options->pam_service_name == NULL) options->pam_service_name = xstrdup(SSHD_PAM_SERVICE); +#endif /* Standard Options */ if (options->num_host_key_files == 0) { @@ -549,6 +565,9 @@ typedef enum { sBadOption, /* == unknown option */ /* Portable-specific options */ sUsePAM, sPAMServiceName, +#ifdef PAM_ENHANCEMENT + sPAMServicePrefix, +#endif /* Standard Options */ sPort, sHostKeyFile, sLoginGraceTime, sPermitRootLogin, sLogFacility, sLogLevel, sLogVerbose, @@ -601,10 +620,20 @@ static struct { /* Portable-specific options */ #ifdef USE_PAM { "usepam", sUsePAM, SSHCFG_GLOBAL }, +#ifdef PAM_ENHANCEMENT + { "pamserviceprefix", sPAMServicePrefix, SSHCFG_GLOBAL }, + { "pamservicename", sPAMServiceName, SSHCFG_GLOBAL }, +#else { "pamservicename", sPAMServiceName, SSHCFG_ALL }, +#endif /* PAM_ENHANCEMENT */ #else { "usepam", sUnsupported, SSHCFG_GLOBAL }, +#ifdef PAM_ENHANCEMENT + { "pamserviceprefix", sUnsupported, SSHCFG_GLOBAL }, + { "pamservicename", sUnsupported, SSHCFG_GLOBAL }, +#else { "pamservicename", sUnsupported, SSHCFG_ALL }, +#endif /* PAM_ENHANCEMENT */ #endif { "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL }, /* Standard Options */ @@ -1365,6 +1394,21 @@ process_server_config_line_depth(ServerO intptr = &options->use_pam; goto parse_flag; #endif +#ifdef PAM_ENHANCEMENT + case sPAMServicePrefix: + charptr = &options->pam_service_prefix; + arg = argv_next(&ac, &av); + 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 (*charptr == NULL) + *charptr = xstrdup(arg); + break; +#endif /* PAM_ENHANCEMENT */ case sPAMServiceName: charptr = &options->pam_service_name; arg = argv_next(&ac, &av); @@ -1372,8 +1416,20 @@ process_server_config_line_depth(ServerO fatal("%s line %d: missing argument.", filename, linenum); } +#ifdef PAM_ENHANCEMENT + if (options->pam_service_prefix != NULL) + fatal("%s line %d: PAMServiceName and PAMServicePrefix" + " are mutually exclusive.", filename, linenum); +#endif /* PAM_ENHANCEMENT */ if (*activep && *charptr == NULL) *charptr = xstrdup(arg); +#ifdef PAM_ENHANCEMENT + /* + * When this option is specified, we will not have + * PAM service for each auth method. + */ + options->pam_service_per_authmethod = 0; +#endif break; /* Standard Options */ diff -wpruN --no-dereference '--exclude=*.orig' a~/servconf.h a/servconf.h --- a~/servconf.h 1970-01-01 00:00:00 +++ a/servconf.h 1970-01-01 00:00:00 @@ -212,6 +212,10 @@ typedef struct { int use_pam; /* Enable auth via PAM */ char *pam_service_name; +#ifdef PAM_ENHANCEMENT + char *pam_service_prefix; + int pam_service_per_authmethod; +#endif int permit_tun; diff -wpruN --no-dereference '--exclude=*.orig' a~/sshd.8 a/sshd.8 --- a~/sshd.8 1970-01-01 00:00:00 +++ a/sshd.8 1970-01-01 00:00:00 @@ -1018,6 +1018,31 @@ concurrently for different ports, this c 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 scp 1 , .Xr sftp 1 , diff -wpruN --no-dereference '--exclude=*.orig' a~/sshd_config.5 a/sshd_config.5 --- a~/sshd_config.5 1970-01-01 00:00:00 +++ a/sshd_config.5 1970-01-01 00:00:00 @@ -1315,6 +1315,7 @@ Available keywords are .Cm MaxAuthTries , .Cm MaxSessions , .Cm PAMServiceName , +.Cm PAMServicePrefix , .Cm PasswordAuthentication , .Cm PermitEmptyPasswords , .Cm PermitListen , @@ -1383,12 +1384,34 @@ key exchange methods. The default is .Pa /etc/moduli . .It Cm PAMServiceName -Specifies the service name used for Pluggable Authentication Modules (PAM) -authentication, authorisation and session controls when -.Cm UsePAM -is enabled. -The default is -.Cm sshd . +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 @@ -2036,8 +2059,7 @@ If is enabled, you will not be able to run .Xr sshd 8 as a non-root user. -The default is -.Cm no . +On OmniOS, 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.