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