From 7291755439ad2f400df51a74b4e9a31a48f484b1 Mon Sep 17 00:00:00 2001 From: Pascal Bellard Date: Tue, 29 Nov 2011 13:51:11 +0100 Subject: httpd: make it possible to use system passwords for auth function old new delta check_user_passwd 320 467 +147 httpd_main 760 757 -3 Signed-off-by: Pascal Bellard Signed-off-by: Denys Vlasenko --- networking/httpd.c | 175 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 147 insertions(+), 28 deletions(-) diff --git a/networking/httpd.c b/networking/httpd.c index ecdf5b5..c66e0f6 100644 --- a/networking/httpd.c +++ b/networking/httpd.c @@ -54,6 +54,8 @@ * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/ * /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/ * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/ + * /adm:root:* # or user root, pwd from /etc/passwd on urls starting with /adm/ + * /wiki:*:* # or any user from /etc/passwd with according pwd on urls starting with /wiki/ * .au:audio/basic # additional mime type for audio.au files * *.php:/path/php # run xxx.php through an interpreter * @@ -123,6 +125,14 @@ //usage: "\n -d STRING URL decode STRING" #include "libbb.h" +#if ENABLE_PAM +/* PAM may include . We may need to undefine bbox's stub define: */ +# undef setlocale +/* For some obscure reason, PAM is not in pam/xxx, but in security/xxx. + * Apparently they like to confuse people. */ +# include +# include +#endif #if ENABLE_FEATURE_HTTPD_USE_SENDFILE # include #endif @@ -1658,6 +1668,56 @@ static int checkPermIP(void) } #if ENABLE_FEATURE_HTTPD_BASIC_AUTH + +# if ENABLE_FEATURE_HTTPD_AUTH_MD5 && ENABLE_PAM +struct pam_userinfo { + const char *name; + const char *pw; +}; + +static int pam_talker(int num_msg, + const struct pam_message **msg, + struct pam_response **resp, + void *appdata_ptr) +{ + int i; + struct pam_userinfo *userinfo = (struct pam_userinfo *) appdata_ptr; + struct pam_response *response; + + if (!resp || !msg || !userinfo) + return PAM_CONV_ERR; + + /* allocate memory to store response */ + response = xzalloc(num_msg * sizeof(*response)); + + /* copy values */ + for (i = 0; i < num_msg; i++) { + const char *s; + + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_ON: + s = userinfo->name; + break; + case PAM_PROMPT_ECHO_OFF: + s = userinfo->pw; + break; + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + s = ""; + break; + default: + free(response); + return PAM_CONV_ERR; + } + response[i].resp = xstrdup(s); + if (PAM_SUCCESS != 0) + response[i].resp_retcode = PAM_SUCCESS; + } + *resp = response; + return PAM_SUCCESS; +} +# endif + /* * Config file entries are of the form "/::". * If config file has no prefix match for path, access is allowed. @@ -1667,7 +1727,7 @@ static int checkPermIP(void) * * Returns 1 if user_and_passwd is OK. */ -static int check_user_passwd(const char *path, const char *user_and_passwd) +static int check_user_passwd(const char *path, char *user_and_passwd) { Htaccess *cur; const char *prev = NULL; @@ -1675,6 +1735,7 @@ static int check_user_passwd(const char *path, const char *user_and_passwd) for (cur = g_auth; cur; cur = cur->next) { const char *dir_prefix; size_t len; + int r; dir_prefix = cur->before_colon; @@ -1690,7 +1751,8 @@ static int check_user_passwd(const char *path, const char *user_and_passwd) len = strlen(dir_prefix); if (len != 1 /* dir_prefix "/" matches all, don't need to check */ && (strncmp(dir_prefix, path, len) != 0 - || (path[len] != '/' && path[len] != '\0')) + || (path[len] != '/' && path[len] != '\0') + ) ) { continue; } @@ -1699,38 +1761,95 @@ static int check_user_passwd(const char *path, const char *user_and_passwd) prev = dir_prefix; if (ENABLE_FEATURE_HTTPD_AUTH_MD5) { - char *md5_passwd; - - md5_passwd = strchr(cur->after_colon, ':'); - if (md5_passwd && md5_passwd[1] == '$' && md5_passwd[2] == '1' - && md5_passwd[3] == '$' && md5_passwd[4] - ) { - char *encrypted; - int r, user_len_p1; - - md5_passwd++; - user_len_p1 = md5_passwd - cur->after_colon; - /* comparing "user:" */ - if (strncmp(cur->after_colon, user_and_passwd, user_len_p1) != 0) { + char *colon_after_user; + const char *passwd; + + colon_after_user = strchr(user_and_passwd, ':'); + if (!colon_after_user) + goto bad_input; + passwd = strchr(cur->after_colon, ':'); + if (!passwd) + goto bad_input; + passwd++; + if (passwd[0] == '*') { +# if ENABLE_PAM + struct pam_userinfo userinfo; + struct pam_conv conv_info = { &pam_talker, (void *) &userinfo }; + pam_handle_t *pamh; + + /* compare "user:" */ + if (cur->after_colon[0] != '*' + && strncmp(cur->after_colon, user_and_passwd, colon_after_user - user_and_passwd + 1) != 0 + ) { + continue; + } + /* this cfg entry is '*' or matches username from peer */ + *colon_after_user = '\0'; + userinfo.name = user_and_passwd; + userinfo.pw = colon_after_user + 1; + r = pam_start("httpd", user_and_passwd, &conv_info, &pamh) != PAM_SUCCESS + || pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK) != PAM_SUCCESS + || pam_acct_mgmt(pamh, PAM_DISALLOW_NULL_AUTHTOK) != PAM_SUCCESS + ; + pam_end(pamh, PAM_SUCCESS); + *colon_after_user = ':'; + goto end_check_passwd; +# else +# if ENABLE_FEATURE_SHADOWPASSWDS + /* Using _r function to avoid pulling in static buffers */ + struct spwd spw; + char buffer[256]; +# endif + struct passwd *pw; + + *colon_after_user = '\0'; + pw = getpwnam(user_and_passwd); + *colon_after_user = ':'; + if (!pw || !pw->pw_passwd) continue; + passwd = pw->pw_passwd; +# if ENABLE_FEATURE_SHADOWPASSWDS + if ((passwd[0] == 'x' || passwd[0] == '*') && !passwd[1]) { + /* getspnam_r may return 0 yet set result to NULL. + * At least glibc 2.4 does this. Be extra paranoid here. */ + struct spwd *result = NULL; + r = getspnam_r(pw->pw_name, &spw, buffer, sizeof(buffer), &result); + if (r == 0 && result) + passwd = result->sp_pwdp; } +# endif +# endif /* ENABLE_PAM */ + } - encrypted = pw_encrypt( - user_and_passwd + user_len_p1 /* cleartext pwd from user */, - md5_passwd /*salt */, 1 /* cleanup */); - r = strcmp(encrypted, md5_passwd); - free(encrypted); - if (r == 0) - goto set_remoteuser_var; /* Ok */ + /* compare "user:" */ + if (cur->after_colon[0] != '*' + && strncmp(cur->after_colon, user_and_passwd, colon_after_user - user_and_passwd + 1) != 0 + ) { continue; } + /* this cfg entry is '*' or matches username from peer */ + + /* encrypt pwd from peer and check match with local one */ + { + char *encrypted = pw_encrypt( + /* pwd: */ colon_after_user + 1, + /* salt: */ passwd, + /* cleanup: */ 0 + ); + r = strcmp(encrypted, passwd); + free(encrypted); + goto end_check_passwd; + } + bad_input: ; } /* Comparing plaintext "user:pass" in one go */ - if (strcmp(cur->after_colon, user_and_passwd) == 0) { - set_remoteuser_var: + r = strcmp(cur->after_colon, user_and_passwd); + end_check_passwd: + if (r == 0) { remoteuser = xstrndup(user_and_passwd, - strchrnul(user_and_passwd, ':') - user_and_passwd); + strchrnul(user_and_passwd, ':') - user_and_passwd + ); return 1; /* Ok */ } } /* for */ @@ -2067,10 +2186,10 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) } #if ENABLE_FEATURE_HTTPD_BASIC_AUTH - /* Case: no "Authorization:" was seen, but page does require passwd. + /* Case: no "Authorization:" was seen, but page might require passwd. * Check that with dummy user:pass */ if (authorized < 0) - authorized = check_user_passwd(urlcopy, ":"); + authorized = check_user_passwd(urlcopy, (char *) ""); if (!authorized) send_headers_and_exit(HTTP_UNAUTHORIZED); #endif @@ -2353,7 +2472,7 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) salt[1] = '1'; salt[2] = '$'; crypt_make_salt(salt + 3, 4); - puts(pw_encrypt(pass, salt, 1)); + puts(pw_encrypt(pass, salt, /*cleanup:*/ 0)); return 0; } #endif -- cgit v1.1