diff --git a/docs/html/config_access.html b/docs/html/config_access.html
index 48cc42d9..167f3239 100644
--- a/docs/html/config_access.html
+++ b/docs/html/config_access.html
@@ -82,6 +82,11 @@ The columns have the following functions:
Enables access to the Configuration tab.
+ Limit Connections
+
+ If nonzero, the user will be limited to this amount of streaming
+ connection at a time.
+
Min Channel Num
If nonzero, the user will only be able to access channels with
diff --git a/src/access.c b/src/access.c
index 1c7c3e18..e6cbebd6 100644
--- a/src/access.c
+++ b/src/access.c
@@ -270,13 +270,14 @@ access_verify(const char *username, const char *password,
if(strcmp(ae->ae_username, username) ||
strcmp(ae->ae_password, password))
continue; /* username/password mismatch */
-
- match = 1;
}
if(!netmask_verify(ae, src))
continue; /* IP based access mismatches */
+ if (ae->ae_username[0] != '*')
+ match = 1;
+
bits |= ae->ae_rights;
}
@@ -295,6 +296,9 @@ access_verify(const char *username, const char *password,
static void
access_update(access_t *a, access_entry_t *ae)
{
+ if(a->aa_conn_limit < ae->ae_conn_limit)
+ a->aa_conn_limit = ae->ae_conn_limit;
+
if(ae->ae_chmin || ae->ae_chmax) {
if(a->aa_chmin || a->aa_chmax) {
if (a->aa_chmin < ae->ae_chmin)
@@ -399,6 +403,14 @@ access_get_hashed(const char *username, const uint8_t digest[20],
SHA_CTX shactx;
uint8_t d[20];
+ if (username) {
+ a->aa_username = strdup(username);
+ a->aa_representative = strdup(username);
+ } else {
+ a->aa_representative = malloc(50);
+ tcp_get_ip_str((struct sockaddr*)src, a->aa_representative, 50);
+ }
+
if(access_noacl) {
a->aa_rights = ACCESS_FULL;
return a;
@@ -418,7 +430,6 @@ access_get_hashed(const char *username, const uint8_t digest[20],
}
}
-
TAILQ_FOREACH(ae, &access_entries, ae_link) {
if(!ae->ae_enabled)
@@ -445,6 +456,8 @@ access_get_hashed(const char *username, const uint8_t digest[20],
/* Username was not matched - no access */
if (!a->aa_match) {
+ free(a->aa_username);
+ a->aa_username = NULL;
if (username && *username != '\0')
a->aa_rights = 0;
}
@@ -1079,6 +1092,12 @@ const idclass_t access_entry_class = {
.name = "Admin",
.off = offsetof(access_entry_t, ae_admin),
},
+ {
+ .type = PT_U32,
+ .id = "conn_limit",
+ .name = "Limit Connections",
+ .off = offsetof(access_entry_t, ae_conn_limit),
+ },
{
.type = PT_U32,
.id = "channel_min",
diff --git a/src/access.h b/src/access.h
index f93667cf..21d62a90 100644
--- a/src/access.h
+++ b/src/access.h
@@ -57,6 +57,8 @@ typedef struct access_entry {
int ae_streaming;
int ae_adv_streaming;
+ uint32_t ae_conn_limit;
+
int ae_dvr;
struct dvr_config *ae_dvr_config;
LIST_ENTRY(access_entry) ae_dvr_config_link;
@@ -99,6 +101,7 @@ typedef struct access {
uint32_t aa_chmax;
htsmsg_t *aa_chtags;
int aa_match;
+ uint32_t aa_conn_limit;
} access_t;
#define ACCESS_ANONYMOUS 0
diff --git a/src/htsp_server.c b/src/htsp_server.c
index 4090a3e3..a7ab5dbe 100644
--- a/src/htsp_server.c
+++ b/src/htsp_server.c
@@ -2202,7 +2202,7 @@ struct {
/**
* Raise privs by field in message
*/
-static void
+static int
htsp_authenticate(htsp_connection_t *htsp, htsmsg_t *m)
{
const char *username;
@@ -2212,7 +2212,7 @@ htsp_authenticate(htsp_connection_t *htsp, htsmsg_t *m)
int privgain;
if((username = htsmsg_get_str(m, "username")) == NULL)
- return;
+ return 0;
if(strcmp(htsp->htsp_username ?: "", username)) {
tvhlog(LOG_INFO, "htsp", "%s: Identified as user %s",
@@ -2223,7 +2223,7 @@ htsp_authenticate(htsp_connection_t *htsp, htsmsg_t *m)
}
if(htsmsg_get_bin(m, "digest", &digest, &digestlen))
- return;
+ return 0;
rights = access_get_hashed(username, digest, htsp->htsp_challenge,
(struct sockaddr *)htsp->htsp_peer);
@@ -2237,6 +2237,7 @@ htsp_authenticate(htsp_connection_t *htsp, htsmsg_t *m)
access_destroy(htsp->htsp_granted_access);
htsp->htsp_granted_access = rights;
+ return privgain;
}
/**
@@ -2281,6 +2282,18 @@ htsp_read_message(htsp_connection_t *htsp, htsmsg_t **mp, int timeout)
return 0;
}
+/*
+ * Status callback
+ */
+static void
+htsp_server_status ( void *opaque, htsmsg_t *m )
+{
+ htsp_connection_t *htsp = opaque;
+ htsmsg_add_str(m, "type", "HTSP");
+ if (htsp->htsp_username)
+ htsmsg_add_str(m, "user", htsp->htsp_username);
+}
+
/**
*
*/
@@ -2290,6 +2303,7 @@ htsp_read_loop(htsp_connection_t *htsp)
htsmsg_t *m = NULL, *reply;
int r, i;
const char *method;
+ void *tcp_id = NULL;;
if(htsp_generate_challenge(htsp)) {
tvhlog(LOG_ERR, "htsp", "%s: Unable to generate challenge",
@@ -2298,10 +2312,18 @@ htsp_read_loop(htsp_connection_t *htsp)
}
pthread_mutex_lock(&global_lock);
+
htsp->htsp_granted_access =
access_get_by_addr((struct sockaddr *)htsp->htsp_peer);
+
+ tcp_id = tcp_connection_launch(htsp->htsp_fd, htsp_server_status,
+ htsp->htsp_granted_access);
+
pthread_mutex_unlock(&global_lock);
+ if (tcp_id == NULL)
+ return 0;
+
tvhlog(LOG_INFO, "htsp", "Got connection from %s", htsp->htsp_logname);
/* Session main loop */
@@ -2312,37 +2334,46 @@ readmsg:
return r;
pthread_mutex_lock(&global_lock);
- htsp_authenticate(htsp, m);
+ if (htsp_authenticate(htsp, m)) {
+ tcp_connection_land(tcp_id);
+ tcp_id = tcp_connection_launch(htsp->htsp_fd, htsp_server_status,
+ htsp->htsp_granted_access);
+ if (tcp_id == NULL) {
+ htsmsg_destroy(m);
+ pthread_mutex_unlock(&global_lock);
+ return 1;
+ }
+ }
if((method = htsmsg_get_str(m, "method")) != NULL) {
tvhtrace("htsp", "%s - method %s", htsp->htsp_logname, method);
for(i = 0; i < NUM_METHODS; i++) {
- if(!strcmp(method, htsp_methods[i].name)) {
+ if(!strcmp(method, htsp_methods[i].name)) {
- if((htsp->htsp_granted_access->aa_rights & htsp_methods[i].privmask) !=
- htsp_methods[i].privmask) {
+ if((htsp->htsp_granted_access->aa_rights &
+ htsp_methods[i].privmask) !=
+ htsp_methods[i].privmask) {
pthread_mutex_unlock(&global_lock);
+ /* Classic authentication failed delay */
+ usleep(250000);
- /* Classic authentication failed delay */
- usleep(250000);
-
- reply = htsmsg_create_map();
- htsmsg_add_u32(reply, "noaccess", 1);
- htsp_reply(htsp, m, reply);
+ reply = htsmsg_create_map();
+ htsmsg_add_u32(reply, "noaccess", 1);
+ htsp_reply(htsp, m, reply);
- htsmsg_destroy(m);
- goto readmsg;
+ htsmsg_destroy(m);
+ goto readmsg;
- } else {
- reply = htsp_methods[i].fn(htsp, m);
- }
- break;
- }
+ } else {
+ reply = htsp_methods[i].fn(htsp, m);
+ }
+ break;
+ }
}
if(i == NUM_METHODS) {
- reply = htsp_error("Method not found");
+ reply = htsp_error("Method not found");
}
} else {
@@ -2356,6 +2387,10 @@ readmsg:
htsmsg_destroy(m);
}
+
+ pthread_mutex_lock(&global_lock);
+ tcp_connection_land(tcp_id);
+ pthread_mutex_unlock(&global_lock);
return 0;
}
@@ -2526,18 +2561,6 @@ htsp_serve(int fd, void **opaque, struct sockaddr_storage *source,
*opaque = NULL;
}
-/*
- * Status callback
- */
-static void
-htsp_server_status ( void *opaque, htsmsg_t *m )
-{
- htsp_connection_t *htsp = opaque;
- htsmsg_add_str(m, "type", "HTSP");
- if (htsp->htsp_username)
- htsmsg_add_str(m, "user", htsp->htsp_username);
-}
-
/*
* Cancel callback
*/
@@ -2556,7 +2579,6 @@ htsp_init(const char *bindaddr)
static tcp_server_ops_t ops = {
.start = htsp_serve,
.stop = NULL,
- .status = htsp_server_status,
.cancel = htsp_server_cancel
};
htsp_server = tcp_server_create(bindaddr, tvheadend_htsp_port, &ops, NULL);
diff --git a/src/http.c b/src/http.c
index dc9e741d..692de1ec 100644
--- a/src/http.c
+++ b/src/http.c
@@ -996,7 +996,7 @@ http_server_init(const char *bindaddr)
static tcp_server_ops_t ops = {
.start = http_serve,
.stop = NULL,
- .status = NULL,
+ .cancel = NULL
};
http_server = tcp_server_create(bindaddr, tvheadend_webui_port, &ops, NULL);
}
diff --git a/src/tcp.c b/src/tcp.c
index cc2cf8ca..47c0d16d 100644
--- a/src/tcp.c
+++ b/src/tcp.c
@@ -34,11 +34,11 @@
#include
#include
-#include "tcp.h"
#include "tvheadend.h"
+#include "tcp.h"
#include "tvhpoll.h"
-#include "queue.h"
#include "notify.h"
+#include "access.h"
int tcp_preferred_address_family = AF_INET;
int tcp_server_running;
@@ -390,6 +390,7 @@ typedef struct tcp_server_launch {
int fd;
tcp_server_ops_t ops;
void *opaque;
+ char *representative;
void (*status) (void *opaque, htsmsg_t *m);
struct sockaddr_storage peer;
struct sockaddr_storage self;
@@ -407,23 +408,51 @@ static LIST_HEAD(, tcp_server_launch) tcp_server_join = { 0 };
*
*/
void *
-tcp_connection_launch(int fd, void (*status) (void *opaque, htsmsg_t *m))
+tcp_connection_launch
+ (int fd, void (*status) (void *opaque, htsmsg_t *m), access_t *aa)
{
- tcp_server_launch_t *tsl;
+ tcp_server_launch_t *tsl, *res = NULL;
+ uint32_t used = 0;
+ time_t started = dispatch_clock;
lock_assert(&global_lock);
assert(status);
+ if (aa == NULL)
+ return NULL;
+
+try_again:
LIST_FOREACH(tsl, &tcp_server_active, alink) {
if (tsl->fd == fd) {
- tsl->status = status;
- LIST_INSERT_HEAD(&tcp_server_launches, tsl, link);
- notify_reload("connections");
- return tsl;
+ res = tsl;
+ if (!aa->aa_conn_limit)
+ break;
+ continue;
}
+ if (!strcmp(aa->aa_representative ?: "", tsl->representative ?: ""))
+ used++;
}
- return NULL;
+
+ if (aa->aa_conn_limit && used >= aa->aa_conn_limit) {
+ if (started + 3 < dispatch_clock) {
+ tvherror("tcp", "multiple connections are not allowed for user '%s' from '%s' (limit %u)",
+ aa->aa_username ?: "", aa->aa_representative ?: "", aa->aa_conn_limit);
+ return NULL;
+ }
+ pthread_mutex_unlock(&global_lock);
+ usleep(250000);
+ pthread_mutex_lock(&global_lock);
+ if (tvheadend_running)
+ goto try_again;
+ return NULL;
+ }
+
+ res->representative = aa->aa_representative ? strdup(aa->aa_representative) : NULL;
+ res->status = status;
+ LIST_INSERT_HEAD(&tcp_server_launches, res, link);
+ notify_reload("connections");
+ return res;
}
/**
@@ -436,8 +465,14 @@ tcp_connection_land(void *id)
lock_assert(&global_lock);
+ if (id == NULL)
+ return;
+
LIST_REMOVE(tsl, link);
notify_reload("connections");
+
+ free(tsl->representative);
+ tsl->representative = NULL;
}
/*
@@ -481,16 +516,10 @@ tcp_server_start(void *aux)
pthread_mutex_lock(&global_lock);
tsl->id = ++tcp_server_launch_id;
if (!tsl->id) tsl->id = ++tcp_server_launch_id;
- if (tsl->ops.status) {
- tsl->status = tsl->ops.status;
- LIST_INSERT_HEAD(&tcp_server_launches, tsl, link);
- notify_reload("connections");
- }
tsl->ops.start(tsl->fd, &tsl->opaque, &tsl->peer, &tsl->self);
/* Stop */
if (tsl->ops.stop) tsl->ops.stop(tsl->opaque);
- if (tsl->ops.status) tcp_connection_land(tsl);
LIST_REMOVE(tsl, alink);
LIST_INSERT_HEAD(&tcp_server_join, tsl, jlink);
pthread_mutex_unlock(&global_lock);
@@ -547,8 +576,10 @@ tcp_server_loop(void *aux)
if(ev.events & TVHPOLL_IN) {
tsl = malloc(sizeof(tcp_server_launch_t));
- tsl->ops = ts->ops;
- tsl->opaque = ts->opaque;
+ tsl->ops = ts->ops;
+ tsl->opaque = ts->opaque;
+ tsl->status = NULL;
+ tsl->representative = NULL;
slen = sizeof(struct sockaddr_storage);
tsl->fd = accept(ts->serverfd,
diff --git a/src/tcp.h b/src/tcp.h
index 0c93e93f..d08adc35 100644
--- a/src/tcp.h
+++ b/src/tcp.h
@@ -43,7 +43,6 @@ typedef struct tcp_server_ops
struct sockaddr_storage *peer,
struct sockaddr_storage *self);
void (*stop) (void *opaque);
- void (*status) (void *opaque, htsmsg_t *m);
void (*cancel) (void *opaque);
} tcp_server_ops_t;
@@ -80,7 +79,10 @@ int tcp_read_timeout(int fd, void *buf, size_t len, int timeout);
char *tcp_get_ip_str(const struct sockaddr *sa, char *s, size_t maxlen);
-void *tcp_connection_launch(int fd, void (*status) (void *opaque, htsmsg_t *m));
+struct access;
+
+void *tcp_connection_launch(int fd, void (*status) (void *opaque, htsmsg_t *m),
+ struct access *aa);
void tcp_connection_land(void *id);
htsmsg_t *tcp_server_connections ( void );
diff --git a/src/webui/static/app/acleditor.js b/src/webui/static/app/acleditor.js
index 2f1426ad..c0406d17 100644
--- a/src/webui/static/app/acleditor.js
+++ b/src/webui/static/app/acleditor.js
@@ -5,8 +5,8 @@
tvheadend.acleditor = function(panel, index)
{
var list = 'enabled,username,password,prefix,streaming,adv_streaming,' +
- 'dvr,dvr_config,webui,admin,channel_min,channel_max,channel_tag,' +
- 'comment';
+ 'dvr,dvr_config,webui,admin,conn_limit,channel_min,channel_max,' +
+ 'channel_tag,comment';
tvheadend.idnode_grid(panel, {
url: 'api/access/entry',
@@ -22,6 +22,7 @@ tvheadend.acleditor = function(panel, index)
dvr: { width: 100 },
webui: { width: 100 },
admin: { width: 100 },
+ conn_limit: { width: 100 },
channel_min: { width: 100 },
channel_max: { width: 100 },
},
diff --git a/src/webui/webui.c b/src/webui/webui.c
index 29240036..2b5916e7 100644
--- a/src/webui/webui.c
+++ b/src/webui/webui.c
@@ -260,7 +260,7 @@ page_static_file(http_connection_t *hc, const char *remain, void *opaque)
}
/**
- * HTTP stream status callback
+ * HTTP subscription handling
*/
static void
http_stream_status ( void *opaque, htsmsg_t *m )
@@ -271,6 +271,18 @@ http_stream_status ( void *opaque, htsmsg_t *m )
htsmsg_add_str(m, "user", hc->hc_username);
}
+static inline void *
+http_stream_preop ( http_connection_t *hc )
+{
+ return tcp_connection_launch(hc->hc_fd, http_stream_status, hc->hc_access);
+}
+
+static inline void
+http_stream_postop ( void *tcp_id )
+{
+ tcp_connection_land(tcp_id);
+}
+
/**
* HTTP stream loop
*/
@@ -288,13 +300,6 @@ http_stream_run(http_connection_t *hc, streaming_queue_t *sq,
struct timeval tp;
int err = 0;
socklen_t errlen = sizeof(err);
- void *tcp_id;
-
- tcp_id = tcp_connection_launch(hc->hc_fd, http_stream_status);
- if (tcp_id == NULL)
- return;
-
- pthread_mutex_unlock(&global_lock);
mux = muxer_create(mc, mcfg);
if(muxer_open_stream(mux, hc->hc_fd))
@@ -416,10 +421,6 @@ http_stream_run(http_connection_t *hc, streaming_queue_t *sq,
muxer_close(mux);
muxer_destroy(mux);
-
- pthread_mutex_lock(&global_lock);
-
- tcp_connection_land(tcp_id);
}
@@ -800,10 +801,15 @@ http_stream_service(http_connection_t *hc, service_t *service, int weight)
size_t qsize;
const char *name;
char addrbuf[50];
+ void *tcp_id;
+ int res = 0;
if(http_access_verify(hc, ACCESS_ADVANCED_STREAMING))
return HTTP_STATUS_UNAUTHORIZED;
+ if((tcp_id = http_stream_preop(hc)) == NULL)
+ return HTTP_STATUS_NOT_ALLOWED;
+
cfg = dvr_config_find_by_name_default(NULL);
/* Build muxer config - this takes the defaults from the default dvr config, which is a hack */
@@ -838,8 +844,12 @@ http_stream_service(http_connection_t *hc, service_t *service, int weight)
http_arg_get(&hc->hc_args, "User-Agent"));
if(s) {
name = tvh_strdupa(service->s_nicename);
+ pthread_mutex_unlock(&global_lock);
http_stream_run(hc, &sq, name, mc, s, &cfg->dvr_muxcnf);
+ pthread_mutex_lock(&global_lock);
subscription_unsubscribe(s);
+ } else {
+ res = HTTP_STATUS_BAD_REQUEST;
}
if(gh)
@@ -850,7 +860,8 @@ http_stream_service(http_connection_t *hc, service_t *service, int weight)
streaming_queue_deinit(&sq);
- return 0;
+ http_stream_postop(tcp_id);
+ return res;
}
/**
@@ -868,10 +879,15 @@ http_stream_mux(http_connection_t *hc, mpegts_mux_t *mm, int weight)
const char *name;
char addrbuf[50];
muxer_config_t muxcfg = { 0 };
+ void *tcp_id;
+ int res = 0;
if(http_access_verify(hc, ACCESS_ADVANCED_STREAMING))
return HTTP_STATUS_UNAUTHORIZED;
+ if((tcp_id = http_stream_preop(hc)) == NULL)
+ return HTTP_STATUS_NOT_ALLOWED;
+
streaming_queue_init(&sq, SMT_PACKET);
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
@@ -881,15 +897,21 @@ http_stream_mux(http_connection_t *hc, mpegts_mux_t *mm, int weight)
SUBSCRIPTION_STREAMING,
addrbuf, hc->hc_username,
http_arg_get(&hc->hc_args, "User-Agent"), NULL);
- if (!s)
- return HTTP_STATUS_BAD_REQUEST;
- name = tvh_strdupa(s->ths_title);
- http_stream_run(hc, &sq, name, MC_RAW, s, &muxcfg);
- subscription_unsubscribe(s);
+ if (s) {
+ name = tvh_strdupa(s->ths_title);
+ pthread_mutex_unlock(&global_lock);
+ http_stream_run(hc, &sq, name, MC_RAW, s, &muxcfg);
+ pthread_mutex_lock(&global_lock);
+ subscription_unsubscribe(s);
+ } else {
+ res = HTTP_STATUS_BAD_REQUEST;
+ }
streaming_queue_deinit(&sq);
- return 0;
+ http_stream_postop(tcp_id);
+
+ return res;
}
#endif
@@ -914,10 +936,15 @@ http_stream_channel(http_connection_t *hc, channel_t *ch, int weight)
size_t qsize;
const char *name;
char addrbuf[50];
+ void *tcp_id;
+ int res = 0;
if (http_access_verify_channel(hc, ACCESS_STREAMING, ch, 1))
return HTTP_STATUS_UNAUTHORIZED;
+ if((tcp_id = http_stream_preop(hc)) == NULL)
+ return HTTP_STATUS_NOT_ALLOWED;
+
cfg = dvr_config_find_by_name_default(NULL);
/* Build muxer config - this takes the defaults from the default dvr config, which is a hack */
@@ -960,8 +987,12 @@ http_stream_channel(http_connection_t *hc, channel_t *ch, int weight)
if(s) {
name = tvh_strdupa(channel_get_name(ch));
+ pthread_mutex_unlock(&global_lock);
http_stream_run(hc, &sq, name, mc, s, &cfg->dvr_muxcnf);
+ pthread_mutex_lock(&global_lock);
subscription_unsubscribe(s);
+ } else {
+ res = HTTP_STATUS_BAD_REQUEST;
}
if(gh)
@@ -977,7 +1008,9 @@ http_stream_channel(http_connection_t *hc, channel_t *ch, int weight)
streaming_queue_deinit(&sq);
- return 0;
+ http_stream_postop(tcp_id);
+
+ return res;
}