/* * Copyright (C) Roman Arutyunyan */ #include #include #include "ngx_rtmp.h" #include "ngx_rtmp_cmd_module.h" static ngx_rtmp_publish_pt next_publish; static ngx_rtmp_play_pt next_play; #define NGX_RTMP_ACCESS_PUBLISH 0x01 #define NGX_RTMP_ACCESS_PLAY 0x02 static char * ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_rtmp_access_postconfiguration(ngx_conf_t *cf); static void * ngx_rtmp_access_create_app_conf(ngx_conf_t *cf); static char * ngx_rtmp_access_merge_app_conf(ngx_conf_t *cf, void *parent, void *child); typedef struct { in_addr_t mask; in_addr_t addr; ngx_uint_t deny; ngx_uint_t flags; } ngx_rtmp_access_rule_t; #if (NGX_HAVE_INET6) typedef struct { struct in6_addr addr; struct in6_addr mask; ngx_uint_t deny; ngx_uint_t flags; } ngx_rtmp_access_rule6_t; #endif typedef struct { ngx_array_t rules; /* array of ngx_rtmp_access_rule_t */ #if (NGX_HAVE_INET6) ngx_array_t rules6; /* array of ngx_rtmp_access_rule6_t */ #endif } ngx_rtmp_access_app_conf_t; static ngx_command_t ngx_rtmp_access_commands[] = { { ngx_string("allow"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12, ngx_rtmp_access_rule, NGX_RTMP_APP_CONF_OFFSET, 0, NULL }, { ngx_string("deny"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12, ngx_rtmp_access_rule, NGX_RTMP_APP_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_rtmp_module_t ngx_rtmp_access_module_ctx = { NULL, /* preconfiguration */ ngx_rtmp_access_postconfiguration, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_rtmp_access_create_app_conf, /* create app configuration */ ngx_rtmp_access_merge_app_conf, /* merge app configuration */ }; ngx_module_t ngx_rtmp_access_module = { NGX_MODULE_V1, &ngx_rtmp_access_module_ctx, /* module context */ ngx_rtmp_access_commands, /* module directives */ NGX_RTMP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static void * ngx_rtmp_access_create_app_conf(ngx_conf_t *cf) { ngx_rtmp_access_app_conf_t *aacf; aacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_access_app_conf_t)); if (aacf == NULL) { return NULL; } if (ngx_array_init(&aacf->rules, cf->pool, 1, sizeof(ngx_rtmp_access_rule_t)) != NGX_OK) { return NULL; } #if (NGX_HAVE_INET6) if (ngx_array_init(&aacf->rules6, cf->pool, 1, sizeof(ngx_rtmp_access_rule6_t)) != NGX_OK) { return NULL; } #endif return aacf; } static ngx_int_t ngx_rtmp_access_merge_rules(ngx_array_t *prev, ngx_array_t *rules) { void *p; if (prev->nelts == 0) { return NGX_OK; } if (rules->nelts == 0) { *rules = *prev; return NGX_OK; } p = ngx_array_push_n(rules, prev->nelts); if (p == NULL) { return NGX_ERROR; } ngx_memcpy(p, prev->elts, prev->size * prev->nelts); return NGX_OK; } static char * ngx_rtmp_access_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_rtmp_access_app_conf_t *prev = parent; ngx_rtmp_access_app_conf_t *conf = child; if (ngx_rtmp_access_merge_rules(&prev->rules, &conf->rules) != NGX_OK) { return NGX_CONF_ERROR; } #if (NGX_HAVE_INET6) if (ngx_rtmp_access_merge_rules(&prev->rules6, &conf->rules6) != NGX_OK) { return NGX_CONF_ERROR; } #endif return NGX_CONF_OK; } static ngx_int_t ngx_rtmp_access_found(ngx_rtmp_session_t *s, ngx_uint_t deny) { if (deny) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "access forbidden by rule"); return NGX_ERROR; } return NGX_OK; } static ngx_int_t ngx_rtmp_access_inet(ngx_rtmp_session_t *s, in_addr_t addr, ngx_uint_t flag) { ngx_uint_t i; ngx_rtmp_access_rule_t *rule; ngx_rtmp_access_app_conf_t *ascf; ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module); rule = ascf->rules.elts; for (i = 0; i < ascf->rules.nelts; i++) { ngx_log_debug3(NGX_LOG_DEBUG_HTTP, s->connection->log, 0, "access: %08XD %08XD %08XD", addr, rule[i].mask, rule[i].addr); if ((addr & rule[i].mask) == rule[i].addr && (flag & rule[i].flags)) { return ngx_rtmp_access_found(s, rule[i].deny); } } return NGX_OK; } #if (NGX_HAVE_INET6) static ngx_int_t ngx_rtmp_access_inet6(ngx_rtmp_session_t *s, u_char *p, ngx_uint_t flag) { ngx_uint_t n; ngx_uint_t i; ngx_rtmp_access_rule6_t *rule6; ngx_rtmp_access_app_conf_t *ascf; ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module); rule6 = ascf->rules6.elts; for (i = 0; i < ascf->rules6.nelts; i++) { #if (NGX_DEBUG) { size_t cl, ml, al; u_char ct[NGX_INET6_ADDRSTRLEN]; u_char mt[NGX_INET6_ADDRSTRLEN]; u_char at[NGX_INET6_ADDRSTRLEN]; cl = ngx_inet6_ntop(p, ct, NGX_INET6_ADDRSTRLEN); ml = ngx_inet6_ntop(rule6[i].mask.s6_addr, mt, NGX_INET6_ADDRSTRLEN); al = ngx_inet6_ntop(rule6[i].addr.s6_addr, at, NGX_INET6_ADDRSTRLEN); ngx_log_debug6(NGX_LOG_DEBUG_HTTP, s->connection->log, 0, "access: %*s %*s %*s", cl, ct, ml, mt, al, at); } #endif for (n = 0; n < 16; n++) { if ((p[n] & rule6[i].mask.s6_addr[n]) != rule6[i].addr.s6_addr[n]) { goto next; } } if (flag & rule6[i].flags) { return ngx_rtmp_access_found(s, rule6[i].deny); } next: continue; } return NGX_OK; } #endif static ngx_int_t ngx_rtmp_access(ngx_rtmp_session_t *s, ngx_uint_t flag) { struct sockaddr_in *sin; ngx_rtmp_access_app_conf_t *ascf; #if (NGX_HAVE_INET6) u_char *p; in_addr_t addr; struct sockaddr_in6 *sin6; #endif ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module); if (ascf == NULL) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, s->connection->log, 0, "access: NULL app conf"); return NGX_ERROR; } /* relay etc */ if (s->connection->sockaddr == NULL) { return NGX_OK; } switch (s->connection->sockaddr->sa_family) { case AF_INET: sin = (struct sockaddr_in *) s->connection->sockaddr; return ngx_rtmp_access_inet(s, sin->sin_addr.s_addr, flag); #if (NGX_HAVE_INET6) case AF_INET6: sin6 = (struct sockaddr_in6 *) s->connection->sockaddr; p = sin6->sin6_addr.s6_addr; if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { addr = p[12] << 24; addr += p[13] << 16; addr += p[14] << 8; addr += p[15]; return ngx_rtmp_access_inet(s, htonl(addr), flag); } return ngx_rtmp_access_inet6(s, p, flag); #endif } return NGX_OK; } static char * ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_rtmp_access_app_conf_t *ascf = conf; ngx_int_t rc; ngx_uint_t all; ngx_str_t *value; ngx_cidr_t cidr; ngx_rtmp_access_rule_t *rule; #if (NGX_HAVE_INET6) ngx_rtmp_access_rule6_t *rule6; #endif size_t n; ngx_uint_t flags; ngx_memzero(&cidr, sizeof(ngx_cidr_t)); value = cf->args->elts; n = 1; flags = 0; if (cf->args->nelts == 2) { flags = NGX_RTMP_ACCESS_PUBLISH | NGX_RTMP_ACCESS_PLAY; } else { for(; n < cf->args->nelts - 1; ++n) { if (value[n].len == sizeof("publish") - 1 && ngx_strcmp(value[1].data, "publish") == 0) { flags |= NGX_RTMP_ACCESS_PUBLISH; continue; } if (value[n].len == sizeof("play") - 1 && ngx_strcmp(value[1].data, "play") == 0) { flags |= NGX_RTMP_ACCESS_PLAY; continue; } ngx_log_error(NGX_LOG_ERR, cf->log, 0, "unexpected access specified: '%V'", &value[n]); return NGX_CONF_ERROR; } } all = (value[n].len == 3 && ngx_strcmp(value[n].data, "all") == 0); if (!all) { rc = ngx_ptocidr(&value[n], &cidr); if (rc == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[1]); return NGX_CONF_ERROR; } if (rc == NGX_DONE) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "low address bits of %V are meaningless", &value[1]); } } switch (cidr.family) { #if (NGX_HAVE_INET6) case AF_INET6: case 0: /* all */ rule6 = ngx_array_push(&ascf->rules6); if (rule6 == NULL) { return NGX_CONF_ERROR; } rule6->mask = cidr.u.in6.mask; rule6->addr = cidr.u.in6.addr; rule6->deny = (value[0].data[0] == 'd') ? 1 : 0; rule6->flags = flags; if (!all) { break; } #endif /* fall through */ default: /* AF_INET */ rule = ngx_array_push(&ascf->rules); if (rule == NULL) { return NGX_CONF_ERROR; } rule->mask = cidr.u.in.mask; rule->addr = cidr.u.in.addr; rule->deny = (value[0].data[0] == 'd') ? 1 : 0; rule->flags = flags; } return NGX_CONF_OK; } static ngx_int_t ngx_rtmp_access_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) { if (s->auto_pushed) { goto next; } if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PUBLISH) != NGX_OK) { return NGX_ERROR; } next: return next_publish(s, v); } static ngx_int_t ngx_rtmp_access_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PLAY) != NGX_OK) { return NGX_ERROR; } return next_play(s, v); } static ngx_int_t ngx_rtmp_access_postconfiguration(ngx_conf_t *cf) { /* chain handlers */ next_publish = ngx_rtmp_publish; ngx_rtmp_publish = ngx_rtmp_access_publish; next_play = ngx_rtmp_play; ngx_rtmp_play = ngx_rtmp_access_play; return NGX_OK; }