1 /* 2 * uhttpd - Tiny single-threaded httpd 3 * 4 * Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org> 5 * Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org> 6 * 7 * Permission to use, copy, modify, and/or distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #define _GNU_SOURCE 21 #define _XOPEN_SOURCE 700 22 #include <strings.h> 23 #ifdef HAVE_SHADOW 24 #include <shadow.h> 25 #endif 26 #include "uhttpd.h" 27 28 /* 29 * Constant-time string comparison to prevent timing-based password oracle. 30 * Returns true only if both strings are equal in length and content. 31 */ 32 static bool uh_pass_compare(const char *a, const char *b) 33 { 34 unsigned int diff = 0; 35 size_t la, lb, i, maxlen; 36 37 if (!a || !b) 38 return false; 39 40 la = strlen(a); 41 lb = strlen(b); 42 maxlen = la > lb ? la : lb; 43 44 for (i = 0; i < maxlen; i++) 45 diff |= (unsigned char)(i < la ? a[i] : 0) ^ 46 (unsigned char)(i < lb ? b[i] : 0); 47 48 diff |= (unsigned int)(la ^ lb); 49 return diff == 0; 50 } 51 52 static LIST_HEAD(auth_realms); 53 54 void uh_auth_add(const char *path, const char *user, const char *pass) 55 { 56 struct auth_realm *new = NULL; 57 struct passwd *pwd; 58 const char *new_pass = NULL; 59 char *dest_path, *dest_user, *dest_pass; 60 61 #ifdef HAVE_SHADOW 62 struct spwd *spwd; 63 #endif 64 65 /* given password refers to a passwd entry */ 66 if ((strlen(pass) > 3) && !strncmp(pass, "$p$", 3)) { 67 #ifdef HAVE_SHADOW 68 /* try to resolve shadow entry */ 69 spwd = getspnam(&pass[3]); 70 if (spwd) 71 new_pass = spwd->sp_pwdp; 72 #endif 73 if (!new_pass) { 74 pwd = getpwnam(&pass[3]); 75 if (pwd && pwd->pw_passwd && pwd->pw_passwd[0]) 76 new_pass = pwd->pw_passwd; 77 } 78 79 /* Only honour a real crypt(3) hash. Anything else from shadow 80 * splits into two cases that mirror login(1) semantics: 81 * 82 * - Empty password field ("root:::..."): the system account 83 * permits passwordless login, so leave new_pass empty and 84 * let the realm be dropped at the next check, which leaves 85 * the protected path unauthenticated. 86 * 87 * - Any other non-'$' value ("*", "x", "!", "!!", "*LK*", 88 * "*NP*", "!hash" lock prefix, ...): the account is locked 89 * or its credential is a placeholder. Without this guard 90 * uh_auth_check's plaintext compare branch would fire (it 91 * runs whenever realm->pass does not start with '$') and a 92 * client could authenticate by sending the placeholder 93 * verbatim as the password. Bind the realm to a sentinel 94 * that starts with '$' (skipping the plaintext branch) and 95 * that crypt(3) cannot produce (failing the hash branch), 96 * so every request gets 401 instead of silent public 97 * access. */ 98 if (!new_pass) { 99 fprintf(stderr, "uhttpd: account '%s' does not " 100 "exist; denying all access to %s\n", 101 &pass[3], path); 102 new_pass = "$"; 103 } else if (new_pass[0] && new_pass[0] != '$') { 104 fprintf(stderr, "uhttpd: account '%s' is locked " 105 "or has a non-crypt password; denying all " 106 "access to %s\n", &pass[3], path); 107 new_pass = "$"; 108 } else if (!new_pass[0]) { 109 fprintf(stderr, "uhttpd: account '%s' has no " 110 "password set; leaving %s unauthenticated\n", 111 &pass[3], path); 112 } 113 } else { 114 new_pass = pass; 115 } 116 117 if (!new_pass || !new_pass[0]) 118 return; 119 120 new = calloc_a(sizeof(*new), 121 &dest_path, strlen(path) + 1, 122 &dest_user, strlen(user) + 1, 123 &dest_pass, strlen(new_pass) + 1); 124 125 if (!new) 126 return; 127 128 new->path = strcpy(dest_path, path); 129 new->user = strcpy(dest_user, user); 130 new->pass = strcpy(dest_pass, new_pass); 131 list_add(&new->list, &auth_realms); 132 } 133 134 bool uh_auth_check(struct client *cl, const char *path, const char *auth, 135 char **uptr, char **pptr) 136 { 137 struct http_request *req = &cl->request; 138 struct auth_realm *realm; 139 bool user_match = false; 140 char *user = NULL; 141 char *pass = NULL; 142 int plen; 143 144 if (uptr) 145 *uptr = NULL; 146 147 if (pptr) 148 *pptr = NULL; 149 150 if (auth && !strncasecmp(auth, "Basic ", 6)) { 151 auth += 6; 152 153 uh_b64decode(uh_buf, sizeof(uh_buf), auth, strlen(auth)); 154 pass = strchr(uh_buf, ':'); 155 if (pass) { 156 user = uh_buf; 157 *pass++ = 0; 158 } 159 } 160 161 req->realm = NULL; 162 plen = strlen(path); 163 list_for_each_entry(realm, &auth_realms, list) { 164 int rlen = strlen(realm->path); 165 166 if (plen < rlen) 167 continue; 168 169 if (strncasecmp(path, realm->path, rlen) != 0) 170 continue; 171 172 req->realm = realm; 173 if (!user) 174 break; 175 176 if (strcmp(user, realm->user) != 0) 177 continue; 178 179 user_match = true; 180 break; 181 } 182 183 if (!req->realm) 184 return true; 185 186 /* The leading '$' check distinguishes a stored plaintext password from a 187 * modern crypt(3) hash ($id$salt$...). It blocks the case where a client 188 * sends the stored hash itself as the password and matches via strcmp. */ 189 if (user_match && 190 ((realm->pass[0] != '$' && uh_pass_compare(pass, realm->pass)) || 191 uh_pass_compare(crypt(pass, realm->pass), realm->pass))) { 192 if (uptr) 193 *uptr = user; 194 195 if (pptr) 196 *pptr = pass; 197 198 return true; 199 } 200 201 uh_http_header(cl, 401, "Authorization Required"); 202 ustream_printf(cl->us, 203 "WWW-Authenticate: Basic realm=\"%s\"\r\n" 204 "Content-Type: text/plain\r\n\r\n", 205 conf.realm); 206 uh_chunk_printf(cl, "Authorization Required\n"); 207 uh_request_done(cl); 208 209 return false; 210 } 211
This page was automatically generated by LXR 0.3.1. • OpenWrt