1 /* Icecast
2 *
3 * This program is distributed under the GNU General Public License, version 2.
4 * A copy of this license is included with this source.
5 *
6 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org,
7 * Michael Smith <msmith@xiph.org>,
8 * oddsock <oddsock@xiph.org>,
9 * Karl Heyes <karl@xiph.org>
10 * and others (see AUTHORS for details).
11 */
12
13 /**
14 * Client authentication functions
15 */
16
17 #ifdef HAVE_CONFIG_H
18 #include <config.h>
19 #endif
20
21 #include <stdlib.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <stdio.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27
28 #include "auth.h"
29 #include "source.h"
30 #include "client.h"
31 #include "cfgfile.h"
32 #include "httpp/httpp.h"
33 #include "md5.h"
34
35 #include "logging.h"
36 #define CATMODULE "auth_htpasswd"
37
38 #ifdef WIN32
39 #define snprintf _snprintf
40 #endif
41
42 static auth_result htpasswd_adduser (auth_t *auth, const char *username, const char *password);
43 static auth_result htpasswd_deleteuser(auth_t *auth, const char *username);
44 static auth_result htpasswd_userlist(auth_t *auth, xmlNodePtr srcnode);
45 static int _free_user (void *key);
46
47 typedef struct
48 {
49 char *name;
50 char *pass;
51 } htpasswd_user;
52
53 typedef struct {
54 char *filename;
55 rwlock_t file_rwlock;
56 avl_tree *users;
57 time_t mtime;
58 } htpasswd_auth_state;
59
htpasswd_clear(auth_t * self)60 static void htpasswd_clear(auth_t *self) {
61 htpasswd_auth_state *state = self->state;
62 free(state->filename);
63 if (state->users)
64 avl_tree_free (state->users, _free_user);
65 thread_rwlock_destroy(&state->file_rwlock);
66 free(state);
67 }
68
69
70 /* md5 hash */
get_hash(const char * data,int len)71 static char *get_hash(const char *data, int len)
72 {
73 struct MD5Context context;
74 unsigned char digest[16];
75
76 MD5Init(&context);
77
78 MD5Update(&context, (const unsigned char *)data, len);
79
80 MD5Final(digest, &context);
81
82 return util_bin_to_hex(digest, 16);
83 }
84
85
compare_users(void * arg,void * a,void * b)86 static int compare_users (void *arg, void *a, void *b)
87 {
88 htpasswd_user *user1 = (htpasswd_user *)a;
89 htpasswd_user *user2 = (htpasswd_user *)b;
90
91 return strcmp (user1->name, user2->name);
92 }
93
94
_free_user(void * key)95 static int _free_user (void *key)
96 {
97 htpasswd_user *user = (htpasswd_user *)key;
98
99 free (user->name); /* ->pass is part of same buffer */
100 free (user);
101 return 1;
102 }
103
104
htpasswd_recheckfile(htpasswd_auth_state * htpasswd)105 static void htpasswd_recheckfile (htpasswd_auth_state *htpasswd)
106 {
107 FILE *passwdfile;
108 avl_tree *new_users;
109 int num = 0;
110 struct stat file_stat;
111 char *sep;
112 char line [MAX_LINE_LEN];
113
114 if (htpasswd->filename == NULL)
115 return;
116 if (stat (htpasswd->filename, &file_stat) < 0)
117 {
118 ICECAST_LOG_WARN("failed to check status of %s", htpasswd->filename);
119
120 /* Create a dummy users tree for things to use later */
121 thread_rwlock_wlock (&htpasswd->file_rwlock);
122 if(!htpasswd->users)
123 htpasswd->users = avl_tree_new(compare_users, NULL);
124 thread_rwlock_unlock (&htpasswd->file_rwlock);
125
126 return;
127 }
128
129 if (file_stat.st_mtime == htpasswd->mtime)
130 {
131 /* common case, no update to file */
132 return;
133 }
134 ICECAST_LOG_INFO("re-reading htpasswd file \"%s\"", htpasswd->filename);
135 passwdfile = fopen (htpasswd->filename, "rb");
136 if (passwdfile == NULL)
137 {
138 ICECAST_LOG_WARN("Failed to open authentication database \"%s\": %s",
139 htpasswd->filename, strerror(errno));
140 return;
141 }
142 htpasswd->mtime = file_stat.st_mtime;
143
144 new_users = avl_tree_new (compare_users, NULL);
145
146 while (get_line(passwdfile, line, MAX_LINE_LEN))
147 {
148 int len;
149 htpasswd_user *entry;
150
151 num++;
152 if(!line[0] || line[0] == '#')
153 continue;
154
155 sep = strrchr (line, ':');
156 if (sep == NULL)
157 {
158 ICECAST_LOG_WARN("No separator on line %d (%s)", num, htpasswd->filename);
159 continue;
160 }
161 entry = calloc (1, sizeof (htpasswd_user));
162 len = strlen (line) + 1;
163 entry->name = malloc (len);
164 *sep = 0;
165 memcpy (entry->name, line, len);
166 entry->pass = entry->name + (sep-line) + 1;
167 avl_insert (new_users, entry);
168 }
169 fclose (passwdfile);
170
171 thread_rwlock_wlock (&htpasswd->file_rwlock);
172 if (htpasswd->users)
173 avl_tree_free (htpasswd->users, _free_user);
174 htpasswd->users = new_users;
175 thread_rwlock_unlock (&htpasswd->file_rwlock);
176 }
177
178
htpasswd_auth(auth_client * auth_user)179 static auth_result htpasswd_auth (auth_client *auth_user)
180 {
181 auth_t *auth = auth_user->client->auth;
182 htpasswd_auth_state *htpasswd = auth->state;
183 client_t *client = auth_user->client;
184 htpasswd_user entry;
185 void *result;
186
187 if (client->username == NULL || client->password == NULL)
188 return AUTH_FAILED;
189
190 if (htpasswd->filename == NULL)
191 {
192 ICECAST_LOG_ERROR("No filename given in options for authenticator.");
193 return AUTH_FAILED;
194 }
195 htpasswd_recheckfile (htpasswd);
196
197 if (htpasswd->users == NULL) {
198 ICECAST_LOG_ERROR("No user list.");
199 return AUTH_FAILED;
200 }
201
202 thread_rwlock_rlock (&htpasswd->file_rwlock);
203 entry.name = client->username;
204 if (avl_get_by_key (htpasswd->users, &entry, &result) == 0)
205 {
206 htpasswd_user *found = result;
207 char *hashed_pw;
208
209 thread_rwlock_unlock (&htpasswd->file_rwlock);
210 hashed_pw = get_hash (client->password, strlen (client->password));
211 if (strcmp (found->pass, hashed_pw) == 0)
212 {
213 free (hashed_pw);
214 return AUTH_OK;
215 }
216 free (hashed_pw);
217 ICECAST_LOG_DEBUG("incorrect password for client");
218 return AUTH_FAILED;
219 }
220 ICECAST_LOG_DEBUG("no such username: %s", client->username);
221 thread_rwlock_unlock (&htpasswd->file_rwlock);
222 return AUTH_FAILED;
223 }
224
225
auth_get_htpasswd_auth(auth_t * authenticator,config_options_t * options)226 int auth_get_htpasswd_auth (auth_t *authenticator, config_options_t *options)
227 {
228 htpasswd_auth_state *state;
229
230 authenticator->authenticate = htpasswd_auth;
231 authenticator->free = htpasswd_clear;
232 authenticator->adduser = htpasswd_adduser;
233 authenticator->deleteuser = htpasswd_deleteuser;
234 authenticator->listuser = htpasswd_userlist;
235
236 state = calloc(1, sizeof(htpasswd_auth_state));
237
238 while(options) {
239 if(!strcmp(options->name, "filename"))
240 {
241 free (state->filename);
242 state->filename = strdup(options->value);
243 }
244 options = options->next;
245 }
246
247 if (state->filename)
248 ICECAST_LOG_INFO("Configured htpasswd authentication using password file \"%s\"",
249 state->filename);
250 else
251 ICECAST_LOG_ERROR("No filename given in options for authenticator.");
252
253 authenticator->state = state;
254
255 thread_rwlock_create(&state->file_rwlock);
256 htpasswd_recheckfile (state);
257
258 return 0;
259 }
260
261
htpasswd_adduser(auth_t * auth,const char * username,const char * password)262 static auth_result htpasswd_adduser (auth_t *auth, const char *username, const char *password)
263 {
264 FILE *passwdfile;
265 char *hashed_password = NULL;
266 htpasswd_auth_state *state = auth->state;
267 htpasswd_user entry;
268 void *result;
269
270 if (state->filename == NULL) {
271 ICECAST_LOG_ERROR("No filename given in options for authenticator.");
272 return AUTH_FAILED;
273 }
274
275 htpasswd_recheckfile (state);
276
277 if (state->filename == NULL) {
278 ICECAST_LOG_ERROR("No user list.");
279 return AUTH_FAILED;
280 }
281
282 thread_rwlock_wlock (&state->file_rwlock);
283
284 entry.name = (char*)username;
285 if (avl_get_by_key (state->users, &entry, &result) == 0)
286 {
287 thread_rwlock_unlock (&state->file_rwlock);
288 return AUTH_USEREXISTS;
289 }
290
291 passwdfile = fopen(state->filename, "ab");
292
293 if (passwdfile == NULL)
294 {
295 thread_rwlock_unlock (&state->file_rwlock);
296 ICECAST_LOG_WARN("Failed to open authentication database \"%s\": %s",
297 state->filename, strerror(errno));
298 return AUTH_FAILED;
299 }
300
301 hashed_password = get_hash(password, strlen(password));
302 if (hashed_password) {
303 fprintf(passwdfile, "%s:%s\n", username, hashed_password);
304 free(hashed_password);
305 }
306
307 fclose(passwdfile);
308 thread_rwlock_unlock (&state->file_rwlock);
309
310 return AUTH_USERADDED;
311 }
312
313
htpasswd_deleteuser(auth_t * auth,const char * username)314 static auth_result htpasswd_deleteuser(auth_t *auth, const char *username)
315 {
316 FILE *passwdfile;
317 FILE *tmp_passwdfile;
318 htpasswd_auth_state *state;
319 char line[MAX_LINE_LEN];
320 char *sep;
321 char *tmpfile = NULL;
322 int tmpfile_len = 0;
323 struct stat file_info;
324
325 state = auth->state;
326
327 if (state->filename == NULL) {
328 ICECAST_LOG_ERROR("No filename given in options for authenticator.");
329 return AUTH_FAILED;
330 }
331
332 if (state->users == NULL) {
333 ICECAST_LOG_ERROR("No user list.");
334 return AUTH_FAILED;
335 }
336
337 thread_rwlock_wlock (&state->file_rwlock);
338 passwdfile = fopen(state->filename, "rb");
339
340 if(passwdfile == NULL) {
341 ICECAST_LOG_WARN("Failed to open authentication database \"%s\": %s",
342 state->filename, strerror(errno));
343 thread_rwlock_unlock (&state->file_rwlock);
344 return AUTH_FAILED;
345 }
346 tmpfile_len = strlen(state->filename) + 6;
347 tmpfile = calloc(1, tmpfile_len);
348 snprintf (tmpfile, tmpfile_len, "%s.tmp", state->filename);
349 if (stat (tmpfile, &file_info) == 0)
350 {
351 ICECAST_LOG_WARN("temp file \"%s\" exists, rejecting operation", tmpfile);
352 free (tmpfile);
353 fclose (passwdfile);
354 thread_rwlock_unlock (&state->file_rwlock);
355 return AUTH_FAILED;
356 }
357
358 tmp_passwdfile = fopen(tmpfile, "wb");
359
360 if(tmp_passwdfile == NULL) {
361 ICECAST_LOG_WARN("Failed to open temporary authentication database \"%s\": %s",
362 tmpfile, strerror(errno));
363 fclose(passwdfile);
364 free(tmpfile);
365 thread_rwlock_unlock (&state->file_rwlock);
366 return AUTH_FAILED;
367 }
368
369
370 while(get_line(passwdfile, line, MAX_LINE_LEN)) {
371 if(!line[0] || line[0] == '#')
372 continue;
373
374 sep = strchr(line, ':');
375 if(sep == NULL) {
376 ICECAST_LOG_DEBUG("No separator in line");
377 continue;
378 }
379
380 *sep = 0;
381 if (strcmp(username, line)) {
382 /* We did not match on the user, so copy it to the temp file */
383 /* and put the : back in */
384 *sep = ':';
385 fprintf(tmp_passwdfile, "%s\n", line);
386 }
387 }
388
389 fclose(tmp_passwdfile);
390 fclose(passwdfile);
391
392 /* Now move the contents of the tmp file to the original */
393 /* Windows won't let us rename a file if the destination file
394 exists...so, lets remove the original first */
395 if (remove(state->filename) != 0) {
396 ICECAST_LOG_ERROR("Problem moving temp authentication file to original \"%s\" - \"%s\": %s",
397 tmpfile, state->filename, strerror(errno));
398 }
399 else {
400 if (rename(tmpfile, state->filename) != 0) {
401 ICECAST_LOG_ERROR("Problem moving temp authentication file to original \"%s\" - \"%s\": %s",
402 tmpfile, state->filename, strerror(errno));
403 }
404 }
405 free(tmpfile);
406 thread_rwlock_unlock (&state->file_rwlock);
407 htpasswd_recheckfile (state);
408
409 return AUTH_USERDELETED;
410 }
411
412
htpasswd_userlist(auth_t * auth,xmlNodePtr srcnode)413 static auth_result htpasswd_userlist(auth_t *auth, xmlNodePtr srcnode)
414 {
415 htpasswd_auth_state *state;
416 xmlNodePtr newnode;
417 avl_node *node;
418
419 state = auth->state;
420
421 if (state->filename == NULL) {
422 ICECAST_LOG_ERROR("No filename given in options for authenticator.");
423 return AUTH_FAILED;
424 }
425
426 htpasswd_recheckfile (state);
427
428 if (state->users == NULL) {
429 ICECAST_LOG_ERROR("No user list.");
430 return AUTH_FAILED;
431 }
432
433 thread_rwlock_rlock (&state->file_rwlock);
434 node = avl_get_first (state->users);
435 while (node)
436 {
437 htpasswd_user *user = (htpasswd_user *)node->key;
438 newnode = xmlNewChild (srcnode, NULL, XMLSTR("User"), NULL);
439 xmlNewTextChild(newnode, NULL, XMLSTR("username"), XMLSTR(user->name));
440 node = avl_get_next (node);
441 }
442 thread_rwlock_unlock (&state->file_rwlock);
443
444 return AUTH_OK;
445 }
446
447