diff --git a/apps/wolfsshd/auth.c b/apps/wolfsshd/auth.c index eadb0a027..32ef89ad6 100644 --- a/apps/wolfsshd/auth.c +++ b/apps/wolfsshd/auth.c @@ -3088,7 +3088,17 @@ int DefaultUserAuthTypes(WOLFSSH* ssh, void* ctx) usr = wolfSSH_GetUsername(ssh); usrConf = wolfSSHD_AuthGetUserConf(authCtx, usr, NULL, NULL, NULL, NULL, NULL); + if (usrConf == NULL && usr != NULL && authCtx->checkUserCb != NULL && + authCtx->checkUserCb(usr) == WSSHD_AUTH_FAILURE) { + /* No such account: compose on the name alone, so Match User + * applies and Match Group cannot. Other failures stay closed. */ + usrConf = wolfSSHD_GetUserConf(authCtx->conf, usr, NULL, 0, + NULL, NULL, NULL, NULL, NULL); + } + if (usrConf == NULL) { + wolfSSH_Log(WS_LOG_ERROR, + "[SSHD] No user configuration; advertising no methods"); ret = 0; } else { diff --git a/apps/wolfsshd/test/test_configuration.c b/apps/wolfsshd/test/test_configuration.c index d8dc67f89..22e62df56 100644 --- a/apps/wolfsshd/test/test_configuration.c +++ b/apps/wolfsshd/test/test_configuration.c @@ -20,6 +20,7 @@ #ifndef _WIN32 #include #include + #include #endif #if !defined(_WIN32) && !(defined(__OSX__) || defined(__APPLE__)) #include @@ -5705,6 +5706,168 @@ static int test_DefaultUserAuthTypesNullArgs(void) return ret; } +/* Probed name; also the Match User target below. */ +#define UNKNOWN_USER "nonexistent_test_user_xyz" + +/* Stubbed account lookup for the probed name, independent of the host */ +enum { + ADV_NO_ACCOUNT, /* getpwnam: no such account */ + ADV_LOOKUP_ERROR, /* getpwnam: lookup error */ + ADV_GROUPS_FAIL, /* account exists, its group list cannot be read */ + ADV_RESOLVED /* account exists with a readable group list */ +}; + +#ifndef _WIN32 +static int s_adv_mode; +static struct passwd s_adv_pw; + +static struct passwd* stub_getpwnam_adv(const char* name) +{ + (void)name; + switch (s_adv_mode) { + case ADV_NO_ACCOUNT: + return NULL; + case ADV_LOOKUP_ERROR: + errno = EIO; + return NULL; + default: + WMEMSET(&s_adv_pw, 0, sizeof(s_adv_pw)); + s_adv_pw.pw_name = (char*)UNKNOWN_USER; + s_adv_pw.pw_uid = 1002; + s_adv_pw.pw_gid = 1002; + s_adv_pw.pw_passwd = (char*)"x"; + return &s_adv_pw; + } +} +#endif + +/* An unknown name advertises what the global directives and Match User + * blocks offer it, so the reply does not reveal which accounts exist. A + * lookup error or unreadable group list advertises nothing. */ +static int test_DefaultUserAuthTypesUnknownUser(void) +{ + int ret = WS_SUCCESS; + int i, j; + + static const struct { + const char* desc; + int mode; + const char* lines[3]; /* directives, NULL terminated */ + int expected; + } vectors[] = { + {"stock config advertises both methods", ADV_NO_ACCOUNT, {NULL}, + WOLFSSH_USERAUTH_PASSWORD | WOLFSSH_USERAUTH_PUBLICKEY}, + {"global PasswordAuthentication no advertises pubkey only", + ADV_NO_ACCOUNT, {"PasswordAuthentication no", NULL}, + WOLFSSH_USERAUTH_PUBLICKEY}, + {"Match User on the probed name applies", ADV_NO_ACCOUNT, + {"Match User " UNKNOWN_USER, "PasswordAuthentication no", NULL}, + WOLFSSH_USERAUTH_PUBLICKEY}, + {"Match User on another name does not apply", ADV_NO_ACCOUNT, + {"Match User someone_else", "PasswordAuthentication no", NULL}, + WOLFSSH_USERAUTH_PASSWORD | WOLFSSH_USERAUTH_PUBLICKEY}, + {"Match Group does not apply to a name with no account", + ADV_NO_ACCOUNT, + {"Match Group somegroup", "PasswordAuthentication no", NULL}, + WOLFSSH_USERAUTH_PASSWORD | WOLFSSH_USERAUTH_PUBLICKEY}, +#ifndef _WIN32 + {"account lookup error advertises nothing", ADV_LOOKUP_ERROR, + {NULL}, 0}, + {"unreadable group list advertises nothing", ADV_GROUPS_FAIL, + {NULL}, 0}, + {"resolved account takes its Match User block", ADV_RESOLVED, + {"Match User " UNKNOWN_USER, "PasswordAuthentication no", NULL}, + WOLFSSH_USERAUTH_PUBLICKEY}, +#endif + }; + const int numVectors = (int)(sizeof(vectors) / sizeof(*vectors)); + const int maxLines = (int)(sizeof(vectors[0].lines) / + sizeof(*vectors[0].lines)); + WOLFSSHD_CONFIG* head; + WOLFSSHD_CONFIG* conf; + WOLFSSHD_AUTH* authCtx; + WOLFSSH_CTX* ctx; + WOLFSSH* ssh; +#ifndef _WIN32 + struct passwd* (*savedGetpwnam)(const char*); + int (*savedGrouplist)(const char*, WGID_T, WGID_T*, int*); + int (*savedSetgroups)(int, const WGID_T*); +#endif + /* keeps the permission drop in wolfSSHD_AuthCreateUser a no-op; see + * test_DefaultUserAuth_OOBRead */ + static const char privSep[] = "UsePrivilegeSeparation no"; + + for (i = 0; i < numVectors && ret == WS_SUCCESS; ++i) { + Log(" Testing scenario: %s.", vectors[i].desc); + + head = wolfSSHD_ConfigNew(NULL); + if (head == NULL) { + Log(" FAILED.\n"); + return WS_MEMORY_E; + } + /* a Match line moves 'conf' to the new block; keep the list head */ + conf = head; + + /* goes in before any Match line so it lands in the global section */ + ret = ParseConfigLine(&conf, privSep, (int)WSTRLEN(privSep), 0); + for (j = 0; ret == WS_SUCCESS && j < maxLines && + vectors[i].lines[j] != NULL; ++j) { + ret = ParseConfigLine(&conf, vectors[i].lines[j], + (int)WSTRLEN(vectors[i].lines[j]), 0); + } + if (ret != WS_SUCCESS) { + Log(" FAILED.\n"); + wolfSSHD_ConfigFree(head); + break; + } + + authCtx = wolfSSHD_AuthCreateUser(NULL, head); + if (authCtx == NULL) { + Log(" FAILED.\n"); + wolfSSHD_ConfigFree(head); + ret = WS_FATAL_ERROR; + break; + } + +#ifndef _WIN32 + s_adv_mode = vectors[i].mode; + savedGetpwnam = wsshd_getpwnam_cb; + wsshd_getpwnam_cb = stub_getpwnam_adv; + InstallGroupStubs(0, &savedGrouplist, &savedSetgroups); + s_grouplist_always_fail = (vectors[i].mode == ADV_GROUPS_FAIL); +#endif + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + ssh = (ctx != NULL) ? wolfSSH_new(ctx) : NULL; + if (ssh == NULL || + wolfSSH_SetUsername(ssh, UNKNOWN_USER) != WS_SUCCESS) { + Log(" FAILED.\n"); + ret = WS_FATAL_ERROR; + } + else if (DefaultUserAuthTypes(ssh, authCtx) != vectors[i].expected) { + Log(" FAILED.\n"); + ret = WS_FATAL_ERROR; + } + else { + Log(" PASSED.\n"); + } + +#ifndef _WIN32 + wsshd_getpwnam_cb = savedGetpwnam; + wsshd_getgrouplist_cb = savedGrouplist; + wsshd_setgroups_cb = savedSetgroups; +#endif + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); + wolfSSHD_AuthFreeUser(authCtx); + wolfSSHD_ConfigFree(head); + } + + return ret; +} + +#undef UNKNOWN_USER + #ifndef _WIN32 /* report a single secure-open scenario; returns WS_SUCCESS when the observed * result matches expectation (wantOk != 0 means expect acceptance) */ @@ -7316,6 +7479,7 @@ const TEST_CASE testCases[] = { TEST_DECL(test_ConfigIncludeMatchChain), TEST_DECL(test_GetUserAuthTypes), TEST_DECL(test_DefaultUserAuthTypesNullArgs), + TEST_DECL(test_DefaultUserAuthTypesUnknownUser), TEST_DECL(test_ConfigSetAuthKeysFile), TEST_DECL(test_ResolveAuthKeysPath), TEST_DECL(test_ConfigFree),