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 2012-2020, Karl Heyes <karl@kheyes.plus.com>
7  * Copyright 2000-2004, Jack Moffitt <jack@xiph.org,
8  *                      Michael Smith <msmith@xiph.org>,
9  *                      oddsock <oddsock@xiph.org>,
10  *                      Karl Heyes <karl@xiph.org>
11  *                      and others (see AUTHORS for details).
12  */
13 
14 /**
15  * Client authentication via command functions
16  *
17  * The stated program is started and via it's stdin it is passed
18  * mountpoint\n
19  * username\n
20  * password\n
21  * a return code of 0 indicates a valid user, authentication failure if
22  * otherwise
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28 
29 #include <stdlib.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <stdio.h>
33 #ifndef WIN32
34 #include <sys/wait.h>
35 #endif
36 #ifdef HAVE_POLL
37 #include <poll.h>
38 #endif
39 #ifdef HAVE_SYS_TYPES_H
40 #include <sys/types.h>
41 #endif
42 #include <signal.h>
43 
44 #include "auth.h"
45 #include "util.h"
46 #include "source.h"
47 #include "client.h"
48 #include "cfgfile.h"
49 #include "httpp/httpp.h"
50 #include "global.h"
51 
52 #include "logging.h"
53 #define CATMODULE "auth_cmd"
54 
55 typedef struct {
56     char *listener_add;
57     char *listener_remove;
58 } auth_cmd;
59 
60 
61 typedef struct
62 {
63     char *location;
64     char errormsg [100];
65 } auth_thread_data;
66 
67 
cmd_clear(auth_t * self)68 static void cmd_clear(auth_t *self)
69 {
70     auth_cmd *cmd = self->state;
71     free (cmd->listener_add);
72     free (cmd->listener_remove);
73     free(cmd);
74 }
75 
process_header(const char * p,auth_client * auth_user)76 static void process_header (const char *p, auth_client *auth_user)
77 {
78     client_t *client = auth_user->client;
79     auth_thread_data *atd = auth_user->thread_data;
80 
81     if (strncasecmp (p, "Mountpoint: ",12) == 0)
82     {
83         char *new_mount = strdup (p+12);
84         if (new_mount)
85         {
86             free (auth_user->mount);
87             auth_user->mount = new_mount;
88         }
89         return;
90     }
91     if (strncasecmp (p, "icecast-slave:", 14) == 0)
92         client->flags |= CLIENT_IS_SLAVE;
93     if (strncasecmp (p, "Location: ", 10) == 0)
94     {
95         int len = strcspn ((char*)p+10, "\r\n");
96         free (atd->location);
97         atd->location = malloc (len+1);
98         snprintf (atd->location, len+1, "%s", (char *)p+10);
99     }
100     if (strncasecmp (p, "ice-username: ", 14) == 0)
101     {
102         int len = strcspn ((char*)p+14, "\r\n");
103         char *name = malloc (len+1);
104         if (name)
105         {
106             snprintf (name, len+1, "%s", (char *)p+14);
107             free (client->username);
108             client->username = name;
109         }
110     }
111 
112     if (strncasecmp (p, "icecast-auth-user: ", 19) == 0)
113     {
114         if (strcmp (p+19, "withintro") == 0)
115             client->flags |= CLIENT_AUTHENTICATED|CLIENT_HAS_INTRO_CONTENT;
116         else if (strcmp (p+19, "1") == 0)
117             client->flags |= CLIENT_AUTHENTICATED;
118         return;
119     }
120     if (strncasecmp (p, "icecast-auth-timelimit: ", 24) == 0)
121     {
122         unsigned limit;
123         sscanf (p+24, "%u", &limit);
124         client->connection.discon.time = time(NULL) + limit;
125     }
126     if (strncasecmp (p, "icecast-auth-message: ", 22) == 0)
127     {
128         char *eol;
129         snprintf (atd->errormsg, sizeof (atd->errormsg), "%s", (char*)p+22);
130         eol = strchr (atd->errormsg, '\r');
131         if (eol == NULL)
132             eol = strchr (atd->errormsg, '\n');
133         if (eol)
134             *eol = '\0';
135     }
136 }
137 
process_body(int fd,pid_t pid,auth_client * auth_user)138 static void process_body (int fd, pid_t pid, auth_client *auth_user)
139 {
140     client_t *client = auth_user->client;
141 
142     if (client->flags & CLIENT_HAS_INTRO_CONTENT)
143     {
144         refbuf_t *head = client->refbuf, *r = head->next;
145         client_t *client = auth_user->client;
146         head->next = NULL;
147         DEBUG0 ("Have intro content from command");
148 
149         while (1)
150         {
151             int ret;
152             unsigned remaining = 4096 - r->len;
153             char *buf = r->data + r->len;
154 
155 #if HAVE_POLL
156             struct pollfd response;
157             response.fd = fd;
158             response.events = POLLIN;
159             response.revents = 0;
160             ret = poll (&response, 1, 1000);
161             if (ret == 0)
162             {
163                 kill (pid, SIGTERM);
164                 WARN1 ("command timeout triggered for %s", auth_user->mount);
165                 return;
166             }
167             if (ret < 0)
168                 continue;
169 #endif
170             ret = read (fd, buf, remaining);
171             if (ret > 0)
172             {
173                 r->len += ret;
174                 if (r->len == 4096)
175                 {
176                     head->next = r;
177                     head = r;
178                     r = refbuf_new (4096);
179                     r->len = 0;
180                 }
181                 continue;
182             }
183             break;
184         }
185         if (r->len)
186             head->next = r;
187         else
188             refbuf_release (r);
189         if (client->refbuf->next == NULL)
190             client->flags &= ~CLIENT_HAS_INTRO_CONTENT;
191     }
192 }
193 
get_response(int fd,auth_client * auth_user,pid_t pid)194 static void get_response (int fd, auth_client *auth_user, pid_t pid)
195 {
196     client_t *client = auth_user->client;
197     refbuf_t *r = client->refbuf;
198     char *buf = r->data, *blankline;
199     unsigned remaining = 4095; /* leave a nul char at least */
200     int ret = 0;
201 
202     memset (r->data, 0, remaining+1);
203     sock_set_blocking (fd, 0);
204     while (remaining)
205     {
206 #if HAVE_POLL
207         struct pollfd response;
208         response.fd = fd;
209         response.events = POLLIN;
210         response.revents = 0;
211         ret = poll (&response, 1, 1000);
212         if (ret == 0)
213         {
214             kill (pid, SIGTERM);
215             WARN1 ("command timeout triggered for %s", auth_user->mount);
216             return;
217         }
218         if (ret < 0)
219             continue;
220         ret = read (fd, buf, remaining);
221 #else
222         if (ret < 0)
223             thread_sleep (20000);
224         ret = read (fd, buf, remaining);
225 #endif
226         if (ret < 0)
227         {
228             if (sock_recoverable (sock_error()))
229                 continue;
230             remaining = 0;
231         }
232         else
233         {
234             remaining -= ret;
235             buf += ret;
236         }
237         if (buf == r->data)   // if no data at all
238             break;
239         blankline = strstr (r->data, "\n\n");
240         if (blankline)
241         {
242             char *p = r->data;
243             do {
244                 char *nl = strchr (p, '\n');
245                 *nl = '\0';
246                 process_header (p, auth_user);
247                 p = nl+1;
248             } while (*p != '\n');
249             if (client->flags & CLIENT_HAS_INTRO_CONTENT)
250             {
251                 r->len = buf - (blankline + 2);
252                 if (r->len)
253                     memmove (r->data, blankline+2, r->len);
254                 client->refbuf = refbuf_new (4096);
255                 client->refbuf->next = r;
256             }
257             process_body (fd, pid, auth_user);
258             return;
259         }
260     }
261     return;
262 }
263 
264 
auth_cmd_client(auth_client * auth_user)265 static auth_result auth_cmd_client (auth_client *auth_user)
266 {
267     int infd[2], outfd[2];
268     pid_t pid;
269     client_t *client = auth_user->client;
270     auth_t *auth = auth_user->auth;
271     auth_cmd *cmd = auth->state;
272     auth_thread_data *atd = auth_user->thread_data;
273     int status, len;
274     const char *qargs;
275     char *referer, *agent, str[512];
276 
277     atd->errormsg[0] = 0;
278     if ((auth->flags & AUTH_RUNNING) == 0)
279         return AUTH_FAILED;
280     if (pipe (infd) < 0 || pipe (outfd) < 0)
281     {
282         ERROR1 ("pipe failed code %d", errno);
283         return AUTH_FAILED;
284     }
285     pid = fork();
286     switch (pid)
287     {
288         case 0: /* child */
289             dup2 (outfd[0], 0);
290             if (outfd[0] != 0)
291                 close (outfd[0]);
292             dup2 (infd[1], 1);
293             if (infd[1] != 1)
294                 close (infd[1]);
295             close (outfd[1]);
296             close (infd[0]);
297 #ifdef _XOPEN_SOURCE
298             if (auth->flags & AUTH_CLEAN_ENV)
299                 unsetenv ("LD_PRELOAD");
300 #endif
301             execl (cmd->listener_add, cmd->listener_add, NULL);
302             exit (-1);
303         case -1:
304             ERROR1 ("Failed to create child process for %s", cmd->listener_add);
305             break;
306         default: /* parent */
307             close (outfd[0]);
308             close (infd[1]);
309             qargs = httpp_getvar (client->parser, HTTPP_VAR_QUERYARGS);
310             agent = (char*)httpp_getvar (client->parser, "user-agent");
311             if (agent)
312                 agent = util_url_escape (agent);
313             referer = (char*)httpp_getvar (client->parser, "referer");
314             if (referer)
315                 referer = util_url_escape (referer);
316             len = snprintf (str, sizeof(str),
317                     "Mountpoint: %s%s\n"
318                     "User: %s\n"
319                     "Pass: %s\n"
320                     "IP: %s\n"
321                     "Agent: %s\n"
322                     "Referer: %s\n\n",
323                     auth_user->mount, qargs ? qargs : "",
324                     client->username ? client->username : "",
325                     client->password ? client->password : "",
326                     client->connection.ip,
327                     agent ? agent : "",
328                     referer ? referer : "");
329             free (agent);
330             free (referer);
331             write (outfd[1], str, len);
332             close (outfd[1]);
333             get_response (infd[0], auth_user, pid);
334             close (infd[0]);
335             status = -1;
336             do
337             {
338                 int wstatus = 0;
339                 DEBUG1 ("Waiting on pid %ld", (long)pid);
340                 if (waitpid (pid, &wstatus, 0) < 0)
341                 {
342                     ERROR1("waitpid error %s", strerror(errno));
343                     break;
344                 }
345                 if (WIFEXITED(wstatus))
346                 {
347                     status = WEXITSTATUS(wstatus);  // should be 8 LSB
348                     break;
349                 }
350                 else if (WIFSIGNALED(wstatus))
351                     break;
352             } while (1);
353 
354             if (status == -1)
355             {
356                 ERROR1 ("unable to exec command \"%s\"", cmd->listener_add);
357                 return AUTH_FAILED;
358             }
359 
360             if (client->flags & CLIENT_AUTHENTICATED)
361                 return AUTH_OK;
362             break;
363     }
364     if (atd->errormsg[0])
365     {
366         INFO3 ("listener %s (%s) returned \"%s\"", client->connection.ip, cmd->listener_add, atd->errormsg);
367         if (atoi (atd->errormsg) == 403)
368         {
369             auth_user->client = NULL;
370             client_send_403 (client, atd->errormsg+4);
371             return AUTH_FAILED;
372         }
373     }
374     if (atd->location)
375     {
376         client_send_302 (client, atd->location);
377         auth_user->client = NULL;
378         free (atd->location);
379         atd->location = NULL;
380     }
381     return AUTH_FAILED;
382 }
383 
auth_cmd_adduser(auth_t * auth,const char * username,const char * password)384 static auth_result auth_cmd_adduser(auth_t *auth, const char *username, const char *password)
385 {
386     return AUTH_FAILED;
387 }
388 
auth_cmd_deleteuser(auth_t * auth,const char * username)389 static auth_result auth_cmd_deleteuser (auth_t *auth, const char *username)
390 {
391     return AUTH_FAILED;
392 }
393 
auth_cmd_listuser(auth_t * auth,xmlNodePtr srcnode)394 static auth_result auth_cmd_listuser (auth_t *auth, xmlNodePtr srcnode)
395 {
396     return AUTH_FAILED;
397 }
398 
399 
alloc_thread_data(auth_t * auth)400 static void *alloc_thread_data (auth_t *auth)
401 {
402     auth_thread_data *atd = calloc (1, sizeof (auth_thread_data));
403     INFO0 ("...handler data initialized");
404     return atd;
405 }
406 
407 
release_thread_data(auth_t * auth,void * thread_data)408 static void release_thread_data (auth_t *auth, void *thread_data)
409 {
410     auth_thread_data *atd = thread_data;
411     free (atd);
412     DEBUG1 ("...handler destroyed for %s", auth->mount);
413 }
414 
415 
auth_get_cmd_auth(auth_t * authenticator,config_options_t * options)416 int auth_get_cmd_auth (auth_t *authenticator, config_options_t *options)
417 {
418     auth_cmd *state;
419 
420     authenticator->authenticate = auth_cmd_client;
421     authenticator->release = cmd_clear;
422     authenticator->adduser = auth_cmd_adduser;
423     authenticator->deleteuser = auth_cmd_deleteuser;
424     authenticator->listuser = auth_cmd_listuser;
425     authenticator->alloc_thread_data = alloc_thread_data;
426     authenticator->release_thread_data = release_thread_data;
427 
428     state = calloc(1, sizeof(auth_cmd));
429 
430     while(options) {
431         if (strcmp (options->name, "listener_add") == 0)
432             state->listener_add = strdup (options->value);
433         if (strcmp (options->name, "listener_remove") == 0)
434             state->listener_remove = strdup (options->value);
435         options = options->next;
436     }
437     if (state->listener_add == NULL)
438     {
439         ERROR0 ("No command specified for authentication");
440         free (state);
441         return -1;
442     }
443     authenticator->state = state;
444     INFO0("external command based authentication setup");
445     return 0;
446 }
447 
448