1 | /* |
---|
2 | * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com> |
---|
3 | * All rights reserved |
---|
4 | * |
---|
5 | * "THE BEER-WARE LICENSE" (Revision 42): |
---|
6 | * Sergey Lyubka wrote this file. As long as you retain this notice you |
---|
7 | * can do whatever you want with this stuff. If we meet some day, and you think |
---|
8 | * this stuff is worth it, you can buy me a beer in return. |
---|
9 | */ |
---|
10 | |
---|
11 | #include "defs.h" |
---|
12 | |
---|
13 | #ifdef HAVE_STRINGS_H |
---|
14 | #include <strings.h> |
---|
15 | #endif |
---|
16 | |
---|
17 | #if !defined(NO_AUTH) |
---|
18 | /* |
---|
19 | * Stringify binary data. Output buffer must be twice as big as input, |
---|
20 | * because each byte takes 2 bytes in string representation |
---|
21 | */ |
---|
22 | static void |
---|
23 | bin2str(char *to, const unsigned char *p, size_t len) |
---|
24 | { |
---|
25 | const char *hex = "0123456789abcdef"; |
---|
26 | |
---|
27 | for (;len--; p++) { |
---|
28 | *to++ = hex[p[0] >> 4]; |
---|
29 | *to++ = hex[p[0] & 0x0f]; |
---|
30 | } |
---|
31 | } |
---|
32 | |
---|
33 | /* |
---|
34 | * Return stringified MD5 hash for list of vectors. |
---|
35 | * buf must point to at least 32-bytes long buffer |
---|
36 | */ |
---|
37 | static void |
---|
38 | md5(char *buf, ...) |
---|
39 | { |
---|
40 | unsigned char hash[16]; |
---|
41 | const struct vec *v; |
---|
42 | va_list ap; |
---|
43 | MD5_CTX ctx; |
---|
44 | int i; |
---|
45 | |
---|
46 | MD5Init(&ctx); |
---|
47 | |
---|
48 | va_start(ap, buf); |
---|
49 | for (i = 0; (v = va_arg(ap, const struct vec *)) != NULL; i++) { |
---|
50 | assert(v->len >= 0); |
---|
51 | if (v->len == 0) |
---|
52 | continue; |
---|
53 | if (i > 0) |
---|
54 | MD5Update(&ctx, (unsigned char *) ":", 1); |
---|
55 | MD5Update(&ctx,(unsigned char *)v->ptr,(unsigned int)v->len); |
---|
56 | } |
---|
57 | va_end(ap); |
---|
58 | |
---|
59 | MD5Final(hash, &ctx); |
---|
60 | bin2str(buf, hash, sizeof(hash)); |
---|
61 | } |
---|
62 | |
---|
63 | /* |
---|
64 | * Compare to vectors. Return 1 if they are equal |
---|
65 | */ |
---|
66 | static int |
---|
67 | vcmp(const struct vec *v1, const struct vec *v2) |
---|
68 | { |
---|
69 | return (v1->len == v2->len && !memcmp(v1->ptr, v2->ptr, v1->len)); |
---|
70 | } |
---|
71 | |
---|
72 | struct digest { |
---|
73 | struct vec user; |
---|
74 | struct vec uri; |
---|
75 | struct vec nonce; |
---|
76 | struct vec cnonce; |
---|
77 | struct vec resp; |
---|
78 | struct vec qop; |
---|
79 | struct vec nc; |
---|
80 | }; |
---|
81 | |
---|
82 | static const struct auth_keyword { |
---|
83 | size_t offset; |
---|
84 | struct vec vec; |
---|
85 | } known_auth_keywords[] = { |
---|
86 | {offsetof(struct digest, user), {"username=", 9}}, |
---|
87 | {offsetof(struct digest, cnonce), {"cnonce=", 7}}, |
---|
88 | {offsetof(struct digest, resp), {"response=", 9}}, |
---|
89 | {offsetof(struct digest, uri), {"uri=", 4}}, |
---|
90 | {offsetof(struct digest, qop), {"qop=", 4}}, |
---|
91 | {offsetof(struct digest, nc), {"nc=", 3}}, |
---|
92 | {offsetof(struct digest, nonce), {"nonce=", 6}}, |
---|
93 | {0, {NULL, 0}} |
---|
94 | }; |
---|
95 | |
---|
96 | static void |
---|
97 | parse_authorization_header(const struct vec *h, struct digest *dig) |
---|
98 | { |
---|
99 | const unsigned char *p, *e, *s; |
---|
100 | struct vec *v, vec; |
---|
101 | const struct auth_keyword *kw; |
---|
102 | |
---|
103 | (void) memset(dig, 0, sizeof(*dig)); |
---|
104 | p = (unsigned char *) h->ptr + 7; |
---|
105 | e = (unsigned char *) h->ptr + h->len; |
---|
106 | |
---|
107 | while (p < e) { |
---|
108 | |
---|
109 | /* Skip spaces */ |
---|
110 | while (p < e && (*p == ' ' || *p == ',')) |
---|
111 | p++; |
---|
112 | |
---|
113 | /* Skip to "=" */ |
---|
114 | for (s = p; s < e && *s != '='; ) |
---|
115 | s++; |
---|
116 | s++; |
---|
117 | |
---|
118 | /* Is it known keyword ? */ |
---|
119 | for (kw = known_auth_keywords; kw->vec.len > 0; kw++) |
---|
120 | if (kw->vec.len <= s - p && |
---|
121 | !memcmp(p, kw->vec.ptr, kw->vec.len)) |
---|
122 | break; |
---|
123 | |
---|
124 | if (kw->vec.len == 0) |
---|
125 | v = &vec; /* Dummy placeholder */ |
---|
126 | else |
---|
127 | v = (struct vec *) ((char *) dig + kw->offset); |
---|
128 | |
---|
129 | if (*s == '"') { |
---|
130 | p = ++s; |
---|
131 | while (p < e && *p != '"') |
---|
132 | p++; |
---|
133 | } else { |
---|
134 | p = s; |
---|
135 | while (p < e && *p != ' ' && *p != ',') |
---|
136 | p++; |
---|
137 | } |
---|
138 | |
---|
139 | v->ptr = (char *) s; |
---|
140 | v->len = p - s; |
---|
141 | |
---|
142 | if (*p == '"') |
---|
143 | p++; |
---|
144 | |
---|
145 | DBG(("auth field [%.*s]", v->len, v->ptr)); |
---|
146 | } |
---|
147 | } |
---|
148 | |
---|
149 | /* |
---|
150 | * Check the user's password, return 1 if OK |
---|
151 | */ |
---|
152 | static int |
---|
153 | check_password(int method, const struct vec *ha1, const struct digest *digest) |
---|
154 | { |
---|
155 | char a2[32], resp[32]; |
---|
156 | struct vec vec_a2; |
---|
157 | |
---|
158 | /* XXX Due to a bug in MSIE, we do not compare the URI */ |
---|
159 | /* Also, we do not check for authentication timeout */ |
---|
160 | if (/*strcmp(dig->uri, c->ouri) != 0 || */ |
---|
161 | digest->resp.len != 32 /*|| |
---|
162 | now - strtoul(dig->nonce, NULL, 10) > 3600 */) |
---|
163 | return (0); |
---|
164 | |
---|
165 | md5(a2, &known_http_methods[method], &digest->uri, NULL); |
---|
166 | vec_a2.ptr = a2; |
---|
167 | vec_a2.len = sizeof(a2); |
---|
168 | md5(resp, ha1, &digest->nonce, &digest->nc, |
---|
169 | &digest->cnonce, &digest->qop, &vec_a2, NULL); |
---|
170 | |
---|
171 | return (!memcmp(resp, digest->resp.ptr, 32)); |
---|
172 | } |
---|
173 | |
---|
174 | static FILE * |
---|
175 | open_auth_file(struct shttpd_ctx *ctx, const char *path) |
---|
176 | { |
---|
177 | char name[FILENAME_MAX]; |
---|
178 | const char *p, *e; |
---|
179 | FILE *fp = NULL; |
---|
180 | int fd; |
---|
181 | |
---|
182 | if (ctx->global_passwd_file) { |
---|
183 | /* Use global passwords file */ |
---|
184 | my_snprintf(name, sizeof(name), "%s", ctx->global_passwd_file); |
---|
185 | } else { |
---|
186 | /* Try to find .htpasswd in requested directory */ |
---|
187 | for (p = path, e = p + strlen(p) - 1; e > p; e--) |
---|
188 | if (IS_DIRSEP_CHAR(*e)) |
---|
189 | break; |
---|
190 | |
---|
191 | assert(IS_DIRSEP_CHAR(*e)); |
---|
192 | (void) my_snprintf(name, sizeof(name), "%.*s/%s", |
---|
193 | (int) (e - p), p, HTPASSWD); |
---|
194 | } |
---|
195 | |
---|
196 | if ((fd = my_open(name, O_RDONLY, 0)) == -1) { |
---|
197 | DBG(("open_auth_file: open(%s)", name)); |
---|
198 | } else if ((fp = fdopen(fd, "r")) == NULL) { |
---|
199 | DBG(("open_auth_file: fdopen(%s)", name)); |
---|
200 | (void) close(fd); |
---|
201 | } |
---|
202 | |
---|
203 | return (fp); |
---|
204 | } |
---|
205 | |
---|
206 | /* |
---|
207 | * Parse the line from htpasswd file. Line should be in form of |
---|
208 | * "user:domain:ha1". Fill in the vector values. Return 1 if successful. |
---|
209 | */ |
---|
210 | static int |
---|
211 | parse_htpasswd_line(const char *s, struct vec *user, |
---|
212 | struct vec *domain, struct vec *ha1) |
---|
213 | { |
---|
214 | user->len = domain->len = ha1->len = 0; |
---|
215 | |
---|
216 | for (user->ptr = s; *s != '\0' && *s != ':'; s++, user->len++); |
---|
217 | if (*s++ != ':') |
---|
218 | return (0); |
---|
219 | |
---|
220 | for (domain->ptr = s; *s != '\0' && *s != ':'; s++, domain->len++); |
---|
221 | if (*s++ != ':') |
---|
222 | return (0); |
---|
223 | |
---|
224 | for (ha1->ptr = s; *s != '\0' && !isspace(* (unsigned char *) s); |
---|
225 | s++, ha1->len++); |
---|
226 | |
---|
227 | DBG(("parse_htpasswd_line: [%.*s] [%.*s] [%.*s]", user->len, user->ptr, |
---|
228 | domain->len, domain->ptr, ha1->len, ha1->ptr)); |
---|
229 | |
---|
230 | return (user->len > 0 && domain->len > 0 && ha1->len > 0); |
---|
231 | } |
---|
232 | |
---|
233 | /* |
---|
234 | * Authorize against the opened passwords file. Return 1 if authorized. |
---|
235 | */ |
---|
236 | static int |
---|
237 | authorize(struct conn *c, FILE *fp) |
---|
238 | { |
---|
239 | struct vec *auth_vec = &c->ch.auth.v_vec; |
---|
240 | struct vec *user_vec = &c->ch.user.v_vec; |
---|
241 | struct vec user, domain, ha1; |
---|
242 | struct digest digest; |
---|
243 | int ok = 0; |
---|
244 | char line[256]; |
---|
245 | |
---|
246 | if (auth_vec->len > 20 && |
---|
247 | !my_strncasecmp(auth_vec->ptr, "Digest ", 7)) { |
---|
248 | |
---|
249 | parse_authorization_header(auth_vec, &digest); |
---|
250 | *user_vec = digest.user; |
---|
251 | |
---|
252 | while (fgets(line, sizeof(line), fp) != NULL) { |
---|
253 | |
---|
254 | if (!parse_htpasswd_line(line, &user, &domain, &ha1)) |
---|
255 | continue; |
---|
256 | |
---|
257 | DBG(("[%.*s] [%.*s] [%.*s]", user.len, user.ptr, |
---|
258 | domain.len, domain.ptr, ha1.len, ha1.ptr)); |
---|
259 | |
---|
260 | if (vcmp(user_vec, &user) && !memcmp(c->ctx->auth_realm, |
---|
261 | domain.ptr, domain.len)) { |
---|
262 | ok = check_password(c->method, &ha1, &digest); |
---|
263 | break; |
---|
264 | } |
---|
265 | } |
---|
266 | } |
---|
267 | |
---|
268 | return (ok); |
---|
269 | } |
---|
270 | |
---|
271 | int |
---|
272 | check_authorization(struct conn *c, const char *path) |
---|
273 | { |
---|
274 | FILE *fp = NULL; |
---|
275 | int authorized = 1; |
---|
276 | |
---|
277 | #ifdef EMBEDDED |
---|
278 | struct llhead *lp; |
---|
279 | struct uri_auth *auth; |
---|
280 | |
---|
281 | /* Check, is this URL protected by shttpd_protect_url() */ |
---|
282 | LL_FOREACH(&c->ctx->uri_auths, lp) { |
---|
283 | auth = LL_ENTRY(lp, struct uri_auth, link); |
---|
284 | if (!strncmp(c->uri, auth->uri, auth->uri_len)) { |
---|
285 | fp = fopen(auth->file_name, "r"); |
---|
286 | break; |
---|
287 | } |
---|
288 | } |
---|
289 | #endif /* EMBEDDED */ |
---|
290 | |
---|
291 | if (fp == NULL) |
---|
292 | fp = open_auth_file(c->ctx, path); |
---|
293 | |
---|
294 | if (fp != NULL) { |
---|
295 | authorized = authorize(c, fp); |
---|
296 | (void) fclose(fp); |
---|
297 | } |
---|
298 | |
---|
299 | return (authorized); |
---|
300 | } |
---|
301 | |
---|
302 | int |
---|
303 | is_authorized_for_put(struct conn *c) |
---|
304 | { |
---|
305 | FILE *fp; |
---|
306 | int ret = 0; |
---|
307 | |
---|
308 | if ((fp = fopen(c->ctx->put_auth_file, "r")) != NULL) { |
---|
309 | ret = authorize(c, fp); |
---|
310 | (void) fclose(fp); |
---|
311 | } |
---|
312 | |
---|
313 | return (ret); |
---|
314 | } |
---|
315 | |
---|
316 | void |
---|
317 | send_authorization_request(struct conn *c) |
---|
318 | { |
---|
319 | char buf[512]; |
---|
320 | |
---|
321 | (void) my_snprintf(buf, sizeof(buf), "Unauthorized\r\n" |
---|
322 | "WWW-Authenticate: Digest qop=\"auth\", realm=\"%s\", " |
---|
323 | "nonce=\"%lu\"", c->ctx->auth_realm, (unsigned long) current_time); |
---|
324 | |
---|
325 | send_server_error(c, 401, buf); |
---|
326 | } |
---|
327 | |
---|
328 | /* |
---|
329 | * Edit the passwords file. |
---|
330 | */ |
---|
331 | int |
---|
332 | edit_passwords(const char *fname, const char *domain, |
---|
333 | const char *user, const char *pass) |
---|
334 | { |
---|
335 | int ret = EXIT_SUCCESS, found = 0; |
---|
336 | struct vec u, d, p; |
---|
337 | char line[512], tmp[FILENAME_MAX], ha1[32]; |
---|
338 | FILE *fp = NULL, *fp2 = NULL; |
---|
339 | |
---|
340 | (void) my_snprintf(tmp, sizeof(tmp), "%s.tmp", fname); |
---|
341 | |
---|
342 | /* Create the file if does not exist */ |
---|
343 | if ((fp = fopen(fname, "a+"))) |
---|
344 | (void) fclose(fp); |
---|
345 | |
---|
346 | /* Open the given file and temporary file */ |
---|
347 | if ((fp = fopen(fname, "r")) == NULL) |
---|
348 | elog(E_FATAL, 0, "Cannot open %s: %s", fname, strerror(errno)); |
---|
349 | else if ((fp2 = fopen(tmp, "w+")) == NULL) |
---|
350 | elog(E_FATAL, 0, "Cannot open %s: %s", tmp, strerror(errno)); |
---|
351 | |
---|
352 | p.ptr = pass; |
---|
353 | p.len = strlen(pass); |
---|
354 | |
---|
355 | /* Copy the stuff to temporary file */ |
---|
356 | while (fgets(line, sizeof(line), fp) != NULL) { |
---|
357 | u.ptr = line; |
---|
358 | if ((d.ptr = strchr(line, ':')) == NULL) |
---|
359 | continue; |
---|
360 | u.len = d.ptr - u.ptr; |
---|
361 | d.ptr++; |
---|
362 | if (strchr(d.ptr, ':') == NULL) |
---|
363 | continue; |
---|
364 | d.len = strchr(d.ptr, ':') - d.ptr; |
---|
365 | |
---|
366 | if ((int) strlen(user) == u.len && |
---|
367 | !memcmp(user, u.ptr, u.len) && |
---|
368 | (int) strlen(domain) == d.len && |
---|
369 | !memcmp(domain, d.ptr, d.len)) { |
---|
370 | found++; |
---|
371 | md5(ha1, &u, &d, &p, NULL); |
---|
372 | (void) fprintf(fp2, "%s:%s:%.32s\n", user, domain, ha1); |
---|
373 | } else { |
---|
374 | (void) fprintf(fp2, "%s", line); |
---|
375 | } |
---|
376 | } |
---|
377 | |
---|
378 | /* If new user, just add it */ |
---|
379 | if (found == 0) { |
---|
380 | u.ptr = user; u.len = strlen(user); |
---|
381 | d.ptr = domain; d.len = strlen(domain); |
---|
382 | md5(ha1, &u, &d, &p, NULL); |
---|
383 | (void) fprintf(fp2, "%s:%s:%.32s\n", user, domain, ha1); |
---|
384 | } |
---|
385 | |
---|
386 | /* Close files */ |
---|
387 | (void) fclose(fp); |
---|
388 | (void) fclose(fp2); |
---|
389 | |
---|
390 | /* Put the temp file in place of real file */ |
---|
391 | (void) my_remove(fname); |
---|
392 | (void) my_rename(tmp, fname); |
---|
393 | |
---|
394 | return (ret); |
---|
395 | } |
---|
396 | #endif /* NO_AUTH */ |
---|