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