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