• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/uhttpd/auth.c

  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