<dd>
Enables access to the Configuration tab.
+ <dt>Limit Connections
+ <dd>
+ If nonzero, the user will be limited to this amount of streaming
+ connection at a time.
+
<dt>Min Channel Num
<dd>
If nonzero, the user will only be able to access channels with
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;
}
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)
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;
}
}
-
TAILQ_FOREACH(ae, &access_entries, ae_link) {
if(!ae->ae_enabled)
/* 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;
}
.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",
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;
uint32_t aa_chmax;
htsmsg_t *aa_chtags;
int aa_match;
+ uint32_t aa_conn_limit;
} access_t;
#define ACCESS_ANONYMOUS 0
/**
* Raise privs by field in message
*/
-static void
+static int
htsp_authenticate(htsp_connection_t *htsp, htsmsg_t *m)
{
const char *username;
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",
}
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);
access_destroy(htsp->htsp_granted_access);
htsp->htsp_granted_access = rights;
+ return privgain;
}
/**
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);
+}
+
/**
*
*/
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",
}
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 */
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);
+
+ reply = htsmsg_create_map();
+ htsmsg_add_u32(reply, "noaccess", 1);
+ htsp_reply(htsp, m, reply);
- /* Classic authentication failed delay */
- usleep(250000);
-
- reply = htsmsg_create_map();
- htsmsg_add_u32(reply, "noaccess", 1);
- htsp_reply(htsp, m, reply);
-
- htsmsg_destroy(m);
- goto readmsg;
-
- } else {
- reply = htsp_methods[i].fn(htsp, m);
- }
- break;
- }
+ htsmsg_destroy(m);
+ goto readmsg;
+
+ } 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 {
htsmsg_destroy(m);
}
+
+ pthread_mutex_lock(&global_lock);
+ tcp_connection_land(tcp_id);
+ pthread_mutex_unlock(&global_lock);
return 0;
}
*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
*/
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);
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);
}
#include <netinet/tcp.h>
#include <arpa/inet.h>
-#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;
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;
*
*/
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;
}
/**
lock_assert(&global_lock);
+ if (id == NULL)
+ return;
+
LIST_REMOVE(tsl, link);
notify_reload("connections");
+
+ free(tsl->representative);
+ tsl->representative = NULL;
}
/*
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);
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,
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;
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 );
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',
dvr: { width: 100 },
webui: { width: 100 },
admin: { width: 100 },
+ conn_limit: { width: 100 },
channel_min: { width: 100 },
channel_max: { width: 100 },
},
}
/**
- * HTTP stream status callback
+ * HTTP subscription handling
*/
static void
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
*/
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))
muxer_close(mux);
muxer_destroy(mux);
-
- pthread_mutex_lock(&global_lock);
-
- tcp_connection_land(tcp_id);
}
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 */
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)
streaming_queue_deinit(&sq);
- return 0;
+ http_stream_postop(tcp_id);
+ return res;
}
/**
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);
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
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 */
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)
streaming_queue_deinit(&sq);
- return 0;
+ http_stream_postop(tcp_id);
+
+ return res;
}