* /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
*
//usage: "\n -d STRING URL decode STRING"
#include "libbb.h"
+#if ENABLE_PAM
+/* PAM may include <locale.h>. 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 <security/pam_appl.h>
+# include <security/pam_misc.h>
+#endif
#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
# include <sys/sendfile.h>
#endif
}
#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 "/<path>:<user>:<passwd>".
* If config file has no prefix match for path, access is allowed.
*
* 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;
for (cur = g_auth; cur; cur = cur->next) {
const char *dir_prefix;
size_t len;
+ int r;
dir_prefix = cur->before_colon;
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;
}
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 */
}
#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
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