auth_url.c 24.4 KB
Newer Older
Karl Heyes's avatar
Karl Heyes committed
1
2
3
4
5
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
6
 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org>,
Karl Heyes's avatar
Karl Heyes committed
7
8
9
10
 *                      Michael Smith <msmith@xiph.org>,
 *                      oddsock <oddsock@xiph.org>,
 *                      Karl Heyes <karl@xiph.org>
 *                      and others (see AUTHORS for details).
11
 * Copyright 2011-2018, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
Karl Heyes's avatar
Karl Heyes committed
12
13
 */

14
/*
Karl Heyes's avatar
Karl Heyes committed
15
16
17
18
19
20
 * Client authentication via URL functions
 *
 * authenticate user via a URL, this is done via libcurl so https can also
 * be handled. The request will have POST information about the request in
 * the form of
 *
21
 * action=listener_add&client=1&server=host&port=8000&mount=/live&user=fred&pass=mypass&ip=127.0.0.1&agent=""
Karl Heyes's avatar
Karl Heyes committed
22
23
24
25
26
27
 *
 * For a user to be accecpted the following HTTP header needs
 * to be returned (the actual string can be specified in the xml file)
 *
 * icecast-auth-user: 1
 *
28
29
30
31
32
33
 * A listening client may also be configured as only to stay connected for a
 * certain length of time. eg The auth server may only allow a 15 minute
 * playback by sending back.
 *
 * icecast-auth-timelimit: 900
 *
Karl Heyes's avatar
Karl Heyes committed
34
35
36
 * On client disconnection another request can be sent to a URL with the POST
 * information of
 *
37
 * action=listener_remove&server=host&port=8000&client=1&mount=/live&user=fred&pass=mypass&duration=3600
Karl Heyes's avatar
Karl Heyes committed
38
39
40
41
42
43
 *
 * client refers to the icecast client identification number. mount refers
 * to the mountpoint (beginning with / and may contain query parameters eg ?&
 * encoded) and duration is the amount of time in seconds. user and pass
 * setting can be blank
 *
44
45
46
47
48
49
50
51
 * On source client connection, a request can be made to trigger a URL request
 * to verify the details externally. Post info is
 *
 * action=stream_auth&mount=/stream&ip=IP&server=SERVER&port=8000&user=fred&pass=pass
 *
 * As admin requests can come in for a stream (eg metadata update) these requests
 * can be issued while stream is active. For these &admin=1 is added to the POST
 * details.
Karl Heyes's avatar
Karl Heyes committed
52
53
54
55
56
57
58
59
60
61
62
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#ifndef _WIN32
63
64
#   include <sys/wait.h>
#   include <strings.h>
Karl Heyes's avatar
Karl Heyes committed
65
#else
66
67
#   define snprintf _snprintf
#   define strncasecmp strnicmp
Karl Heyes's avatar
Karl Heyes committed
68
69
#endif

70
#include "util.h"
71
#include "curl.h"
Karl Heyes's avatar
Karl Heyes committed
72
73
74
75
#include "auth.h"
#include "source.h"
#include "client.h"
#include "cfgfile.h"
76
#include "connection.h"
Marvin Scholz's avatar
Marvin Scholz committed
77
#include "common/httpp/httpp.h"
Karl Heyes's avatar
Karl Heyes committed
78
79
80
81

#include "logging.h"
#define CATMODULE "auth_url"

82
/* Default headers */
83
84
85
86
87
88
89
90
#define DEFAULT_HEADER_OLD_RESULT           "icecast-auth-user: 1\r\n"
#define DEFAULT_HEADER_OLD_TIMELIMIT        "icecast-auth-timelimit:"
#define DEFAULT_HEADER_OLD_MESSAGE          "icecast-auth-message"
#define DEFAULT_HEADER_NEW_RESULT           "x-icecast-auth-result"
#define DEFAULT_HEADER_NEW_TIMELIMIT        "x-icecast-auth-timelimit"
#define DEFAULT_HEADER_NEW_MESSAGE          "x-icecast-auth-message"
#define DEFAULT_HEADER_NEW_ALTER_ACTION     "x-icecast-auth-alter-action"
#define DEFAULT_HEADER_NEW_ALTER_ARGUMENT   "x-icecast-auth-alter-argument"
91

Karl Heyes's avatar
Karl Heyes committed
92
typedef struct {
Marvin Scholz's avatar
Marvin Scholz committed
93
94
95
96
97
98
99
100
    char       *pass_headers; // headers passed from client to addurl.
    char       *prefix_headers; // prefix for passed headers.
    char       *addurl;
    char       *removeurl;
    char       *addaction;
    char       *removeaction;
    char       *username;
    char       *password;
101
102

    /* old style */
Marvin Scholz's avatar
Marvin Scholz committed
103
    char       *auth_header;
104
    size_t      auth_header_len;
Marvin Scholz's avatar
Marvin Scholz committed
105
    char       *timelimit_header;
106
107
108
109
    size_t      timelimit_header_len;
    /* new style */
    char       *header_auth;
    char       *header_timelimit;
110
    char       *header_message;
111
112
    char       *header_alter_action;
    char       *header_alter_argument;
113

Marvin Scholz's avatar
Marvin Scholz committed
114
115
116
    char       *userpwd;
    CURL       *handle;
    char        errormsg[CURL_ERROR_SIZE];
Philipp Schafft's avatar
Philipp Schafft committed
117
    auth_result result;
Karl Heyes's avatar
Karl Heyes committed
118
119
} auth_url;

120
121
122
123
124
typedef struct {
    char *all_headers;
    size_t all_headers_len;
    http_parser_t *parser;
} auth_user_url_t;
Karl Heyes's avatar
Karl Heyes committed
125

126
127
128
129
130
131
132
static inline const char * __str_or_default(const char *str, const char *def)
{
    if (str)
        return str;
    return def;
}

Karl Heyes's avatar
Karl Heyes committed
133
static void auth_url_clear(auth_t *self)
134
135
{
    auth_url *url;
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
136

137
    ICECAST_LOG_INFO("Doing auth URL cleanup");
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
138
    url = self->state;
139
    self->state = NULL;
140
    icecast_curl_free(url->handle);
Philipp Schafft's avatar
Philipp Schafft committed
141
142
143
144
145
146
147
148
149
150
    free(url->username);
    free(url->password);
    free(url->pass_headers);
    free(url->prefix_headers);
    free(url->removeurl);
    free(url->addurl);
    free(url->addaction);
    free(url->removeaction);
    free(url->auth_header);
    free(url->timelimit_header);
151
152
    free(url->header_auth);
    free(url->header_timelimit);
153
    free(url->header_message);
154
155
    free(url->header_alter_action);
    free(url->header_alter_argument);
Philipp Schafft's avatar
Philipp Schafft committed
156
157
    free(url->userpwd);
    free(url);
Karl Heyes's avatar
Karl Heyes committed
158
159
}

160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
static void auth_user_url_clear(auth_client *auth_user)
{
    auth_user_url_t *au_url = auth_user->authbackend_userdata;

    if (!au_url)
        return;

    free(au_url->all_headers);
    if (au_url->parser)
        httpp_destroy(au_url->parser);

    free(au_url);
    auth_user->authbackend_userdata = NULL;
}

static void handle_returned_header__complete(auth_client *auth_user)
{
    auth_user_url_t *au_url = auth_user->authbackend_userdata;
    const char *tmp;
179
180
    const char *action;
    const char *argument;
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
    auth_url *url = auth_user->client->auth->state;

    if (!au_url)
        return;

    if (au_url->parser)
        return;

    au_url->parser = httpp_create_parser();
    httpp_initialize(au_url->parser, NULL);

    if (!httpp_parse_response(au_url->parser, au_url->all_headers, au_url->all_headers_len, NULL)) {
        ICECAST_LOG_ERROR("Can not parse auth backend reply.");
        return;
    }

    tmp = httpp_getvar(au_url->parser, HTTPP_VAR_ERROR_CODE);
    if (tmp[0] == '2') {
        ICECAST_LOG_DEBUG("Got final status: %#H", tmp);
    } else {
        ICECAST_LOG_DEBUG("Got non-final status: %#H", tmp);
        httpp_destroy(au_url->parser);
        au_url->parser = NULL;
        au_url->all_headers_len = 0;
        return;
    }

    if (url->header_auth) {
        tmp = httpp_getvar(au_url->parser, url->header_auth);
        if (tmp) {
            url->result = auth_str2result(tmp);
        }
    }

    if (url->header_timelimit) {
        tmp = httpp_getvar(au_url->parser, url->header_timelimit);
        if (tmp) {
            long long int ret;
            char *endptr;

            errno = 0;
            ret = strtoll(tmp, &endptr, 0);
            if (endptr != tmp && errno == 0) {
                auth_user->client->con->discon_time = time(NULL) + (time_t)ret;
            } else {
                ICECAST_LOG_ERROR("Auth backend returned invalid new style timelimit header: % #H", tmp);
            }
        }
    }

231
232
233
234
235
236
237
238
239
240
241
    action   = httpp_getvar(au_url->parser, __str_or_default(url->header_alter_action, DEFAULT_HEADER_NEW_ALTER_ACTION));
    argument = httpp_getvar(au_url->parser, __str_or_default(url->header_alter_argument, DEFAULT_HEADER_NEW_ALTER_ARGUMENT));

    if (action && argument) {
        if (auth_alter_client(auth_user->client->auth, auth_user, auth_str2alter(action), argument) != 0) {
            ICECAST_LOG_ERROR("Auth backend returned invalid alter action/argument.");
        }
    } else if (action || argument) {
        ICECAST_LOG_ERROR("Auth backend returned incomplete alter action/argument.");
    }

242
243
244
245
246
247
248
    if (url->header_message) {
        tmp = httpp_getvar(au_url->parser, url->header_message);
    } else {
        tmp = httpp_getvar(au_url->parser, DEFAULT_HEADER_NEW_MESSAGE);
        if (!tmp)
            tmp = httpp_getvar(au_url->parser, DEFAULT_HEADER_OLD_MESSAGE);
    }
249
250
251
252
253
    if (tmp) {
        snprintf(url->errormsg, sizeof(url->errormsg), "%s", tmp);
    }
}

Marvin Scholz's avatar
Marvin Scholz committed
254
255
256
257
static size_t handle_returned_header(void      *ptr,
                                     size_t    size,
                                     size_t    nmemb,
                                     void      *stream)
Karl Heyes's avatar
Karl Heyes committed
258
{
259
    size_t len = size * nmemb;
Karl Heyes's avatar
Karl Heyes committed
260
261
    auth_client *auth_user = stream;
    client_t *client = auth_user->client;
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
    auth_t *auth;
    auth_url *url;

    if (!client)
        return len;

    auth = client->auth;
    url = auth->state;

    if (!auth_user->authbackend_userdata) {
        auth_user->authbackend_userdata = calloc(1, sizeof(auth_user_url_t));
    }

    if (auth_user->authbackend_userdata) {
        auth_user_url_t *au_url = auth_user->authbackend_userdata;
        char *n = realloc(au_url->all_headers, au_url->all_headers_len + len);
        if (n) {
            au_url->all_headers = n;
            memcpy(n + au_url->all_headers_len, ptr, len);
            au_url->all_headers_len += len;
        } else {
            ICECAST_LOG_ERROR("Can not allocate buffer for auth backend reply headers. BAD.");
        }
    } else {
        ICECAST_LOG_ERROR("Can not allocate authbackend_userdata. BAD.");
    }
Karl Heyes's avatar
Karl Heyes committed
288

289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
    ICECAST_LOG_DEBUG("Got header: %* #H", (int)(size * nmemb + 2), ptr);

    if (url->auth_header && len >= url->auth_header_len && strncasecmp(ptr, url->auth_header, url->auth_header_len) == 0) {
        url->result = AUTH_OK;
    }

    if (url->timelimit_header && len > url->timelimit_header_len && strncasecmp(ptr, url->timelimit_header, url->timelimit_header_len) == 0) {
        const char *input = ptr;
        unsigned int limit = 0;

        if (len >= 2 && input[len - 2] == '\r' && input[len - 1] == '\n') {
            input += url->timelimit_header_len;

            if (sscanf(input, "%u\r\n", &limit) == 1) {
                client->con->discon_time = time(NULL) + limit;
            } else {
                ICECAST_LOG_ERROR("Auth backend returned invalid timeline header: Can not parse limit");
            }
        } else {
            ICECAST_LOG_ERROR("Auth backend returned invalid timelimit header.");
        }
    }

    if (len == 1) {
        const char *c = ptr;
        if (c[0] == '\r' || c[0] == '\n') {
            handle_returned_header__complete(auth_user);
316
        }
317
318
319
320
    } else if (len == 2) {
        const char *c = ptr;
        if ((c[0] == '\r' || c[0] == '\n') && (c[1] == '\r' || c[1] == '\n')) {
            handle_returned_header__complete(auth_user);
Karl Heyes's avatar
Karl Heyes committed
321
322
323
        }
    }

324
325

    return len;
Karl Heyes's avatar
Karl Heyes committed
326
327
}

Marvin Scholz's avatar
Marvin Scholz committed
328
static auth_result url_remove_client(auth_client *auth_user)
Karl Heyes's avatar
Karl Heyes committed
329
{
Marvin Scholz's avatar
Marvin Scholz committed
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
    client_t       *client      = auth_user->client;
    auth_t         *auth        = client->auth;
    auth_url       *url         = auth->state;
    time_t          duration    = time(NULL) - client->con->con_time;
    char           *username,
                   *password,
                   *mount,
                   *server;
    const char     *mountreq;
    ice_config_t   *config;
    int             port;
    char           *userpwd     = NULL,
                    post[4096];
    const char     *agent;
    char           *user_agent,
                   *ipaddr;
346
    int             ret;
Karl Heyes's avatar
Karl Heyes committed
347

348
349
    if (url->removeurl == NULL)
        return AUTH_OK;
350

Marvin Scholz's avatar
Marvin Scholz committed
351
352
    config = config_get_config();
    server = util_url_escape(config->hostname);
Karl Heyes's avatar
Karl Heyes committed
353
    port = config->port;
Marvin Scholz's avatar
Marvin Scholz committed
354
    config_release_config();
Karl Heyes's avatar
Karl Heyes committed
355

Marvin Scholz's avatar
Marvin Scholz committed
356
357
358
359
360
361
    agent = httpp_getvar(client->parser, "user-agent");
    if (agent) {
        user_agent = util_url_escape(agent);
    } else {
        user_agent = strdup("-");
    }
362

Marvin Scholz's avatar
Marvin Scholz committed
363
364
365
366
367
    if (client->username) {
        username = util_url_escape(client->username);
    } else {
        username = strdup("");
    }
Karl Heyes's avatar
Karl Heyes committed
368

Marvin Scholz's avatar
Marvin Scholz committed
369
370
371
372
373
    if (client->password) {
        password = util_url_escape(client->password);
    } else {
        password = strdup("");
    }
Karl Heyes's avatar
Karl Heyes committed
374
375

    /* get the full uri (with query params if available) */
Marvin Scholz's avatar
Marvin Scholz committed
376
    mountreq = httpp_getvar(client->parser, HTTPP_VAR_RAWURI);
377
    if (mountreq == NULL)
Marvin Scholz's avatar
Marvin Scholz committed
378
379
380
        mountreq = httpp_getvar(client->parser, HTTPP_VAR_URI);
    mount = util_url_escape(mountreq);
    ipaddr = util_url_escape(client->con->ip);
Karl Heyes's avatar
Karl Heyes committed
381

382
    ret = snprintf(post, sizeof(post),
Philipp Schafft's avatar
Philipp Schafft committed
383
            "action=%s&server=%s&port=%d&client=%lu&mount=%s"
384
            "&user=%s&pass=%s&duration=%lu&ip=%s&agent=%s",
Philipp Schafft's avatar
Philipp Schafft committed
385
            url->removeaction, /* already escaped */
Karl Heyes's avatar
Karl Heyes committed
386
            server, port, client->con->id, mount, username,
387
            password, (long unsigned)duration, ipaddr, user_agent);
Marvin Scholz's avatar
Marvin Scholz committed
388
389
390
391
392
393
394
395

    free(server);
    free(mount);
    free(username);
    free(password);
    free(ipaddr);
    free(user_agent);

396
397
398
399
400
401
    if (ret <= 0 || ret >= (ssize_t)sizeof(post)) {
        ICECAST_LOG_ERROR("Authentication failed for client %p as header POST data is too long.", client);
        auth_user_url_clear(auth_user);
        return AUTH_FAILED;
    }

Marvin Scholz's avatar
Marvin Scholz committed
402
403
404
405
    if (strchr (url->removeurl, '@') == NULL) {
        if (url->userpwd) {
            curl_easy_setopt(url->handle, CURLOPT_USERPWD, url->userpwd);
        } else {
406
            /* auth'd requests may not have a user/pass, but may use query args */
Marvin Scholz's avatar
Marvin Scholz committed
407
408
409
410
411
412
413
414
415
            if (client->username && client->password) {
                size_t len = strlen(client->username) +
                    strlen(client->password) + 2;
                userpwd = malloc(len);
                snprintf(userpwd, len, "%s:%s",
                    client->username, client->password);
                curl_easy_setopt(url->handle, CURLOPT_USERPWD, userpwd);
            } else {
                curl_easy_setopt(url->handle, CURLOPT_USERPWD, "");
416
417
            }
        }
Marvin Scholz's avatar
Marvin Scholz committed
418
    } else {
419
        /* url has user/pass but libcurl may need to clear any existing settings */
Marvin Scholz's avatar
Marvin Scholz committed
420
        curl_easy_setopt(url->handle, CURLOPT_USERPWD, "");
421
    }
Marvin Scholz's avatar
Marvin Scholz committed
422
423
424
    curl_easy_setopt(url->handle, CURLOPT_URL, url->removeurl);
    curl_easy_setopt(url->handle, CURLOPT_POSTFIELDS, post);
    curl_easy_setopt(url->handle, CURLOPT_WRITEHEADER, auth_user);
Karl Heyes's avatar
Karl Heyes committed
425
426

    if (curl_easy_perform (url->handle))
Marvin Scholz's avatar
Marvin Scholz committed
427
428
        ICECAST_LOG_WARN("auth to server %s failed with %s",
            url->removeurl, url->errormsg);
Karl Heyes's avatar
Karl Heyes committed
429

Marvin Scholz's avatar
Marvin Scholz committed
430
    free(userpwd);
431
    auth_user_url_clear(auth_user);
432

Karl Heyes's avatar
Karl Heyes committed
433
434
435
436
    return AUTH_OK;
}


Marvin Scholz's avatar
Marvin Scholz committed
437
static auth_result url_add_client(auth_client *auth_user)
Karl Heyes's avatar
Karl Heyes committed
438
{
Marvin Scholz's avatar
Marvin Scholz committed
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
    client_t       *client      = auth_user->client;
    auth_t         *auth        = client->auth;
    auth_url       *url         = auth->state;
    int             res         = 0,
                    port;
    const char     *agent;
    char           *user_agent,
                   *username,
                   *password;
    const char     *mountreq;
    char           *mount,
                   *ipaddr,
                   *server;
    ice_config_t   *config;
    char           *userpwd    = NULL, post [4096];
    ssize_t         post_offset;
    char           *pass_headers,
                   *cur_header,
                   *next_header;
    const char     *header_val;
    char           *header_valesc;
Karl Heyes's avatar
Karl Heyes committed
460
461
462
463

    if (url->addurl == NULL)
        return AUTH_OK;

Marvin Scholz's avatar
Marvin Scholz committed
464
465
    config = config_get_config();
    server = util_url_escape(config->hostname);
Karl Heyes's avatar
Karl Heyes committed
466
    port = config->port;
Marvin Scholz's avatar
Marvin Scholz committed
467
    config_release_config();
468

Marvin Scholz's avatar
Marvin Scholz committed
469
470
471
472
473
474
    agent = httpp_getvar(client->parser, "user-agent");
    if (agent) {
        user_agent = util_url_escape(agent);
    } else {
        user_agent = strdup("-");
    }
475

Marvin Scholz's avatar
Marvin Scholz committed
476
477
478
479
480
    if (client->username) {
        username = util_url_escape(client->username);
    } else {
        username = strdup("");
    }
481

Marvin Scholz's avatar
Marvin Scholz committed
482
483
484
485
486
    if (client->password) {
        password = util_url_escape(client->password);
    } else {
        password = strdup("");
    }
Karl Heyes's avatar
Karl Heyes committed
487
488

    /* get the full uri (with query params if available) */
Marvin Scholz's avatar
Marvin Scholz committed
489
    mountreq = httpp_getvar(client->parser, HTTPP_VAR_RAWURI);
490
    if (mountreq == NULL)
Marvin Scholz's avatar
Marvin Scholz committed
491
492
493
        mountreq = httpp_getvar(client->parser, HTTPP_VAR_URI);
    mount = util_url_escape(mountreq);
    ipaddr = util_url_escape(client->con->ip);
Karl Heyes's avatar
Karl Heyes committed
494

Marvin Scholz's avatar
Marvin Scholz committed
495
    post_offset = snprintf(post, sizeof (post),
Philipp Schafft's avatar
Philipp Schafft committed
496
            "action=%s&server=%s&port=%d&client=%lu&mount=%s"
Karl Heyes's avatar
Karl Heyes committed
497
            "&user=%s&pass=%s&ip=%s&agent=%s",
Philipp Schafft's avatar
Philipp Schafft committed
498
            url->addaction, /* already escaped */
Karl Heyes's avatar
Karl Heyes committed
499
500
            server, port, client->con->id, mount, username,
            password, ipaddr, user_agent);
Marvin Scholz's avatar
Marvin Scholz committed
501
502
503
504
505
506
507

    free(server);
    free(mount);
    free(user_agent);
    free(username);
    free(password);
    free(ipaddr);
Karl Heyes's avatar
Karl Heyes committed
508

509
510
511
512
513
514
515

    if (post_offset <= 0 || post_offset >= (ssize_t)sizeof(post)) {
        ICECAST_LOG_ERROR("Authentication failed for client %p as header POST data is too long.", client);
        auth_user_url_clear(auth_user);
        return AUTH_FAILED;
    }

516
517
    pass_headers = NULL;
    if (url->pass_headers)
Marvin Scholz's avatar
Marvin Scholz committed
518
519
        pass_headers = strdup(url->pass_headers);
    if (pass_headers) {
520
        cur_header = pass_headers;
Marvin Scholz's avatar
Marvin Scholz committed
521
522
523
        while (cur_header) {
            next_header = strstr(cur_header, ",");
            if (next_header) {
524
                *next_header=0;
525
                next_header++;
526
            }
527
528

            header_val = httpp_getvar (client->parser, cur_header);
Marvin Scholz's avatar
Marvin Scholz committed
529
            if (header_val) {
530
531
532
                size_t left = sizeof(post) - post_offset;
                int ret;

533
                header_valesc = util_url_escape (header_val);
534
                ret = snprintf(post + post_offset,
Marvin Scholz's avatar
Marvin Scholz committed
535
536
537
538
539
                                        sizeof(post) - post_offset,
                                        "&%s%s=%s",
                                        url->prefix_headers ? url->prefix_headers : "",
                                        cur_header, header_valesc);
                free(header_valesc);
540
541
542
543
544
545
546
547
548

                if (ret <= 0 || (size_t)ret >= left) {
                    ICECAST_LOG_ERROR("Authentication failed for client %p as header \"%H\" is too long.", client, cur_header);
                    free(pass_headers);
                    auth_user_url_clear(auth_user);
                    return AUTH_FAILED;
                } else {
                    post_offset += ret;
                }
549
550
            }

551
            cur_header = next_header;
552
        }
553
        free(pass_headers);
554
555
    }

Marvin Scholz's avatar
Marvin Scholz committed
556
557
558
559
    if (strchr(url->addurl, '@') == NULL) {
        if (url->userpwd) {
            curl_easy_setopt(url->handle, CURLOPT_USERPWD, url->userpwd);
        } else {
560
            /* auth'd requests may not have a user/pass, but may use query args */
Marvin Scholz's avatar
Marvin Scholz committed
561
562
            if (client->username && client->password) {
                size_t len = strlen(client->username) + strlen(client->password) + 2;
563
                userpwd = malloc (len);
Marvin Scholz's avatar
Marvin Scholz committed
564
565
566
567
                snprintf(userpwd, len, "%s:%s",
                    client->username, client->password);
                curl_easy_setopt(url->handle, CURLOPT_USERPWD, userpwd);
            } else {
568
                curl_easy_setopt (url->handle, CURLOPT_USERPWD, "");
Marvin Scholz's avatar
Marvin Scholz committed
569
            }
570
        }
Marvin Scholz's avatar
Marvin Scholz committed
571
    } else {
572
        /* url has user/pass but libcurl may need to clear any existing settings */
Marvin Scholz's avatar
Marvin Scholz committed
573
        curl_easy_setopt(url->handle, CURLOPT_USERPWD, "");
574
    }
Marvin Scholz's avatar
Marvin Scholz committed
575
576
577
    curl_easy_setopt(url->handle, CURLOPT_URL, url->addurl);
    curl_easy_setopt(url->handle, CURLOPT_POSTFIELDS, post);
    curl_easy_setopt(url->handle, CURLOPT_WRITEHEADER, auth_user);
Karl Heyes's avatar
Karl Heyes committed
578
579
    url->errormsg[0] = '\0';

Philipp Schafft's avatar
Philipp Schafft committed
580
    url->result = AUTH_FAILED;
Marvin Scholz's avatar
Marvin Scholz committed
581
    res = curl_easy_perform(url->handle);
Karl Heyes's avatar
Karl Heyes committed
582

Marvin Scholz's avatar
Marvin Scholz committed
583
    free(userpwd);
584
    auth_user_url_clear(auth_user);
585

Marvin Scholz's avatar
Marvin Scholz committed
586
587
588
    if (res) {
        ICECAST_LOG_WARN("auth to server %s failed with %s",
            url->addurl, url->errormsg);
Karl Heyes's avatar
Karl Heyes committed
589
590
591
        return AUTH_FAILED;
    }
    /* we received a response, lets see what it is */
Philipp Schafft's avatar
Philipp Schafft committed
592
    if (url->result == AUTH_FAILED) {
Marvin Scholz's avatar
Marvin Scholz committed
593
594
        ICECAST_LOG_INFO("client auth (%s) failed with \"%s\"",
            url->addurl, url->errormsg);
595
    }
Philipp Schafft's avatar
Philipp Schafft committed
596
    return url->result;
597
598
}

Marvin Scholz's avatar
Marvin Scholz committed
599
600
601
static auth_result auth_url_adduser(auth_t      *auth,
                                    const char  *username,
                                    const char  *password)
Karl Heyes's avatar
Karl Heyes committed
602
603
604
605
{
    return AUTH_FAILED;
}

Marvin Scholz's avatar
Marvin Scholz committed
606
static auth_result auth_url_deleteuser(auth_t *auth, const char *username)
Karl Heyes's avatar
Karl Heyes committed
607
608
609
610
{
    return AUTH_FAILED;
}

Marvin Scholz's avatar
Marvin Scholz committed
611
static auth_result auth_url_listuser(auth_t *auth, xmlNodePtr srcnode)
Karl Heyes's avatar
Karl Heyes committed
612
613
614
615
{
    return AUTH_FAILED;
}

616
int auth_get_url_auth(auth_t *authenticator, config_options_t *options)
Karl Heyes's avatar
Karl Heyes committed
617
{
Marvin Scholz's avatar
Marvin Scholz committed
618
619
620
    auth_url    *url_info;
    const char  *addaction      = "listener_add";
    const char  *removeaction   = "listener_remove";
Karl Heyes's avatar
Karl Heyes committed
621

Marvin Scholz's avatar
Marvin Scholz committed
622
623
624
625
    authenticator->free         = auth_url_clear;
    authenticator->adduser      = auth_url_adduser;
    authenticator->deleteuser   = auth_url_deleteuser;
    authenticator->listuser     = auth_url_listuser;
Karl Heyes's avatar
Karl Heyes committed
626

Marvin Scholz's avatar
Marvin Scholz committed
627
628
    url_info                    = calloc(1, sizeof(auth_url));
    authenticator->state        = url_info;
629

630
    /* force auth thread to call function. this makes sure the auth_t is attached to client */
Philipp Schafft's avatar
Philipp Schafft committed
631
    authenticator->authenticate_client = url_add_client;
632

Karl Heyes's avatar
Karl Heyes committed
633
    while(options) {
Philipp Schafft's avatar
Philipp Schafft committed
634
        if(strcmp(options->name, "username") == 0) {
635
            replace_string(&(url_info->username), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
636
        } else if(strcmp(options->name, "password") == 0) {
637
            replace_string(&(url_info->password), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
638
        } else if(strcmp(options->name, "headers") == 0) {
639
            replace_string(&(url_info->pass_headers), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
640
        } else if(strcmp(options->name, "header_prefix") == 0) {
641
            replace_string(&(url_info->prefix_headers), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
642
        } else if(strcmp(options->name, "client_add") == 0) {
643
            replace_string(&(url_info->addurl), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
644
645
        } else if(strcmp(options->name, "client_remove") == 0) {
            authenticator->release_client = url_remove_client;
646
            replace_string(&(url_info->removeurl), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
647
648
649
650
651
        } else if(strcmp(options->name, "action_add") == 0) {
            addaction = options->value;
        } else if(strcmp(options->name, "action_remove") == 0) {
            removeaction = options->value;
        } else if(strcmp(options->name, "auth_header") == 0) {
652
            replace_string(&(url_info->auth_header), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
653
        } else if (strcmp(options->name, "timelimit_header") == 0) {
654
            replace_string(&(url_info->timelimit_header), options->value);
655
656
        } else if (strcmp(options->name, "header_auth") == 0) {
            replace_string(&(url_info->header_auth), options->value);
657
            util_strtolower(url_info->header_message);
658
659
        } else if (strcmp(options->name, "header_timelimit") == 0) {
            replace_string(&(url_info->header_timelimit), options->value);
660
            util_strtolower(url_info->header_message);
661
662
        } else if (strcmp(options->name, "header_message") == 0) {
            replace_string(&(url_info->header_message), options->value);
663
            util_strtolower(url_info->header_message);
664
665
666
667
668
669
        } else if (strcmp(options->name, "header_alter_action") == 0) {
            replace_string(&(url_info->header_alter_action), options->value);
            util_strtolower(url_info->header_alter_action);
        } else if (strcmp(options->name, "header_alter_argument") == 0) {
            replace_string(&(url_info->header_alter_argument), options->value);
            util_strtolower(url_info->header_alter_argument);
Philipp Schafft's avatar
Philipp Schafft committed
670
671
        } else {
            ICECAST_LOG_ERROR("Unknown option: %s", options->name);
672
        }
Karl Heyes's avatar
Karl Heyes committed
673
674
        options = options->next;
    }
Philipp Schafft's avatar
Philipp Schafft committed
675
676
677
678

    url_info->addaction = util_url_escape(addaction);
    url_info->removeaction = util_url_escape(removeaction);

679
    url_info->handle = icecast_curl_new(NULL, &url_info->errormsg[0]);
Marvin Scholz's avatar
Marvin Scholz committed
680
681
    if (url_info->handle == NULL) {
        auth_url_clear(authenticator);
Karl Heyes's avatar
Karl Heyes committed
682
683
        return -1;
    }
Philipp Schafft's avatar
Philipp Schafft committed
684

685
    /* default headers */
686
687
688
689
690
691
692
693
694
695
696
697
    if (url_info->auth_header) {
        ICECAST_LOG_WARN("You use old style auth option \"auth_header\". Please switch to new style option \"header_auth\".");
    } else if (!url_info->header_auth && !url_info->auth_header) {
        ICECAST_LOG_WARN("You do not have enabled old or new style auth option for auth status header. I will enable both. Please set \"header_auth\".");
        url_info->auth_header = strdup(DEFAULT_HEADER_OLD_RESULT);
        url_info->header_auth = strdup(DEFAULT_HEADER_NEW_RESULT);
    }
    if (url_info->timelimit_header) {
        ICECAST_LOG_WARN("You use old style auth option \"timelimit_header\". Please switch to new style option \"header_timelimit\".");
    } else if (!url_info->header_timelimit && !url_info->timelimit_header) {
        ICECAST_LOG_WARN("You do not have enabled old or new style auth option for auth timelimit header. I will enable both. Please set \"header_timelimit\".");
        url_info->timelimit_header = strdup(DEFAULT_HEADER_OLD_TIMELIMIT);
698
        url_info->header_timelimit = strdup(DEFAULT_HEADER_NEW_TIMELIMIT);
699
    }
700

Karl Heyes's avatar
Karl Heyes committed
701
702
    if (url_info->auth_header)
        url_info->auth_header_len = strlen (url_info->auth_header);
703
704
    if (url_info->timelimit_header)
        url_info->timelimit_header_len = strlen (url_info->timelimit_header);
Karl Heyes's avatar
Karl Heyes committed
705

Marvin Scholz's avatar
Marvin Scholz committed
706
    curl_easy_setopt(url_info->handle, CURLOPT_HEADERFUNCTION, handle_returned_header);
Karl Heyes's avatar
Karl Heyes committed
707

Marvin Scholz's avatar
Marvin Scholz committed
708
709
710
711
712
    if (url_info->username && url_info->password) {
        int len = strlen(url_info->username) + strlen(url_info->password) + 2;
        url_info->userpwd = malloc(len);
        snprintf(url_info->userpwd, len, "%s:%s",
            url_info->username, url_info->password);
713
714
    }

715
    ICECAST_LOG_INFO("URL based authentication setup");
Karl Heyes's avatar
Karl Heyes committed
716
717
    return 0;
}