@cryptotaxi247 / netdata-1 / commits / 187620911

Allow hostnames in Access Control Lists (#6796)

##### Summary The Access Control List (ACL) configuration parameters can now use hostnames with simple patterns. Incoming connections are resolved using reverse DNS to obtain the hostname. Where a hostname is resolved, forward DNS resolution is performed to check the IP address is really associated with the hostname. If the checks pass then the patterns supplied are checked. Any patterns supplied for numeric ip addresses are also checked. ##### Component Name daemon ##### Additional Information Fixes #6438 * Reverse lookup on ip to get hostname * Forward lookup on hostname to get IP addresses. * Validation that the incomming ip is associated with the host. If these checks fail the hostname is discarded so it cannot match against the access-list patterns. If these checks validate the ip successfully then the resolved hostname is pattern-matched as described in the previous commit.

Andrew Moss committed Sep 18, 2019 at 16:45 UTC 187620911c06585882350ff871ef49bb6e6acd1e
7 files changed +189 -52
docs/netdata-security.md
+35
@@ -86,6 +86,41 @@ In Netdata v1.9+ there is also access list support, like this:
86 allow connections from = localhost 10.* 192.168.*
87 ```
88
89 +#### Fine-grainined access control
90 +
91 +The access list support allows filtering of all incoming connections, by specific IP addresses, ranges
92 +or validated DNS lookups. Only connections that match an entry on the list will be allowed:
93 +
94 +```
95 +[web]
96 + allow connections from = localhost 192.168.* 1.2.3.4 homeip.net
97 +```
98 +
99 +Connections from the IP addresses are allowed if the connection IP matches one of the patterns given.
100 +The alias localhost is alway checked against 127.0.0.1, any other symbolic names need to resolve in
101 +both directions using DNS. In the above example the IP address of `homeip.net` must reverse DNS resolve
102 +to the incoming IP address and a DNS lookup on `homeip.net` must return the incoming IP address as
103 +one of the resolved addresses.
104 +
105 +More specific control of what each incoming connection can do can be specified through the access control
106 +list settings:
107 +
108 +```
109 +[web]
110 + allow connections from = 160.1.*
111 + allow badges from = 160.1.1.2
112 + allow streaming from = 160.1.2.*
113 + allow management from = control.subnet.ip
114 + allow netdata.conf from = updates.subnet.ip
115 + allow dashboard from = frontend.subnet.ip
116 +```
117 +
118 +In this example only connections from `160.1.x.x` are allowed, only the specific IP address `160.1.1.2`
119 +can access badges, only IP addresses in the smaller range `160.1.2.x` can stream data. The three
120 +hostnames shown can access specific features, this assumes that DNS is setup to resolve these names
121 +to IP addresses within the `160.1.x.x` range and that reverse DNS is setup for these hosts.
122 +
123 +
124 #### Use an authenticating web server in proxy mode
125
126 Use one web server to provide authentication in front of **all your Netdata servers**. So, you will be accessing all your Netdata with URLs like `http://{HOST}/netdata/{NETDATA_HOSTNAME}/` and authentication will be shared among all of them (you will sign-in once for all your servers). Instructions are provided on how to set the proxy configuration to have Netdata run behind [nginx](Running-behind-nginx.md), [Apache](Running-behind-apache.md), [lighthttpd](Running-behind-lighttpd.md#netdata-via-lighttpd-v14x) and [Caddy](Running-behind-caddy.md#netdata-via-caddy).
libnetdata/socket/socket.c
+104 -23
@@ -995,21 +995,103 @@ int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags) {
995 }
996 #endif
997
998 +/*
999 + * ---------------------------------------------------------------------------------------------------------------------
1000 + * connection_allowed() - if there is an access list then check the connection matches a pattern.
1001 + * Numeric patterns are checked against the IP address first, only if they
1002 + * do not match is the hostname resolved (reverse-DNS) and checked. If the
1003 + * hostname matches then we perform forward DNS resolution to check the IP
1004 + * is really associated with the DNS record. This call is repeatable: the
1005 + * web server may check more refined matches against the connection. Will
1006 + * update the client_host if uninitialized - ensure the hostsize is the number
1007 + * of *writable* bytes (i.e. be aware of the strdup used to compact the pollinfo).
1008 + */
1009 +extern int connection_allowed(int fd, char *client_ip, char *client_host, size_t hostsize, SIMPLE_PATTERN *access_list,
1010 + const char *patname) {
1011 + if (!access_list)
1012 + return 1;
1013 + if (simple_pattern_matches(access_list, client_ip))
1014 + return 1;
1015 + // If the hostname is unresolved (and needed) then attempt the DNS lookups.
1016 + if (client_host[0]==0)
1017 + {
1018 + struct sockaddr_storage sadr;
1019 + socklen_t addrlen = sizeof(sadr);
1020 + int err = getpeername(fd, (struct sockaddr*)&sadr, &addrlen);
1021 + if (err != 0 ||
1022 + (err = getnameinfo((struct sockaddr *)&sadr, addrlen, client_host, (socklen_t)hostsize,
1023 + NULL, 0, NI_NAMEREQD)) != 0) {
1024 + error("Incoming connection on '%s' does not match a numeric pattern, "
1025 + "and host could not be resolved (err=%s)", client_ip, gai_strerror(err));
1026 + if (hostsize >= 8)
1027 + strcpy(client_host,"UNKNOWN");
1028 + return 0;
1029 + }
1030 + struct addrinfo *addr_infos = NULL;
1031 + if (getaddrinfo(client_host, NULL, NULL, &addr_infos) !=0 ) {
1032 + error("LISTENER: cannot validate hostname '%s' from '%s' by resolving it",
1033 + client_host, client_ip);
1034 + if (hostsize >= 8)
1035 + strcpy(client_host,"UNKNOWN");
1036 + return 0;
1037 + }
1038 + struct addrinfo *scan = addr_infos;
1039 + int validated = 0;
1040 + while (scan) {
1041 + char address[INET6_ADDRSTRLEN];
1042 + address[0] = 0;
1043 + switch (scan->ai_addr->sa_family) {
1044 + case AF_INET:
1045 + inet_ntop(AF_INET, &((struct sockaddr_in*)(scan->ai_addr))->sin_addr, address, INET6_ADDRSTRLEN);
1046 + break;
1047 + case AF_INET6:
1048 + inet_ntop(AF_INET6, &((struct sockaddr_in6*)(scan->ai_addr))->sin6_addr, address, INET6_ADDRSTRLEN);
1049 + break;
1050 + }
1051 + debug(D_LISTENER, "Incoming ip %s rev-resolved onto %s, validating against forward-resolution %s",
1052 + client_ip, client_host, address);
1053 + if (!strcmp(client_ip, address)) {
1054 + validated = 1;
1055 + break;
1056 + }
1057 + scan = scan->ai_next;
1058 + }
1059 + if (!validated) {
1060 + error("LISTENER: Cannot validate '%s' as ip of '%s', not listed in DNS", client_ip, client_host);
1061 + if (hostsize >= 8)
1062 + strcpy(client_host,"UNKNOWN");
1063 + }
1064 + if (addr_infos!=NULL)
1065 + freeaddrinfo(addr_infos);
1066 + }
1067 + if (!simple_pattern_matches(access_list, client_host)) {
1068 + debug(D_LISTENER, "Incoming connection on '%s' (%s) does not match allowed pattern for %s",
1069 + client_ip, client_host, patname);
1070 + return 0;
1071 + }
1072 + return 1;
1073 +}
1074
1075 // --------------------------------------------------------------------------------------------------------------------
1076 // accept_socket() - accept a socket and store client IP and port
1077
1002 -int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *client_port, size_t portsize, SIMPLE_PATTERN *access_list) {
1078 +int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *client_port, size_t portsize,
1079 + char *client_host, size_t hostsize, SIMPLE_PATTERN *access_list) {
1080 struct sockaddr_storage sadr;
1081 socklen_t addrlen = sizeof(sadr);
1082
1083 int nfd = accept4(fd, (struct sockaddr *)&sadr, &addrlen, flags);
1084 if (likely(nfd >= 0)) {
1008 - if (getnameinfo((struct sockaddr *)&sadr, addrlen, client_ip, (socklen_t)ipsize, client_port, (socklen_t)portsize, NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
1085 + if (getnameinfo((struct sockaddr *)&sadr, addrlen, client_ip, (socklen_t)ipsize,
1086 + client_port, (socklen_t)portsize, NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
1087 error("LISTENER: cannot getnameinfo() on received client connection.");
1088 strncpyz(client_ip, "UNKNOWN", ipsize - 1);
1089 strncpyz(client_port, "UNKNOWN", portsize - 1);
1090 }
1091 + if(!strcmp(client_ip, "127.0.0.1") || !strcmp(client_ip, "::1")) {
1092 + strncpy(client_ip, "localhost", ipsize);
1093 + client_ip[ipsize - 1] = '\0';
1094 + }
1095
1096 #ifdef __FreeBSD__
1097 if(((struct sockaddr *)&sadr)->sa_family == AF_LOCAL)
@@ -1044,21 +1126,12 @@ int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *clien
1126 debug(D_LISTENER, "New UNKNOWN web client from %s port %s on socket %d.", client_ip, client_port, fd);
1127 break;
1128 }
1047 -
1048 - if(access_list) {
1049 - if(!strcmp(client_ip, "127.0.0.1") || !strcmp(client_ip, "::1")) {
1050 - strncpy(client_ip, "localhost", ipsize);
1051 - client_ip[ipsize - 1] = '\0';
1052 - }
1053 -
1054 - if(unlikely(!simple_pattern_matches(access_list, client_ip))) {
1055 - errno = 0;
1056 - debug(D_LISTENER, "Permission denied for client '%s', port '%s'", client_ip, client_port);
1057 - error("DENIED ACCESS to client '%s'", client_ip);
1058 - close(nfd);
1059 - nfd = -1;
1060 - errno = EPERM;
1061 - }
1129 + if(!connection_allowed(nfd, client_ip, client_host, hostsize, access_list, "connection")) {
1130 + errno = 0;
1131 + error("Permission denied for client '%s', port '%s'", client_ip, client_port);
1132 + close(nfd);
1133 + nfd = -1;
1134 + errno = EPERM;
1135 }
1136 }
1137 #ifdef HAVE_ACCEPT4
@@ -1084,6 +1157,7 @@ inline POLLINFO *poll_add_fd(POLLJOB *p
1157 , uint32_t flags
1158 , const char *client_ip
1159 , const char *client_port
1160 + , const char *client_host
1161 , void *(*add_callback)(POLLINFO * /*pi*/, short int * /*events*/, void * /*data*/)
1162 , void (*del_callback)(POLLINFO * /*pi*/)
1163 , int (*rcv_callback)(POLLINFO * /*pi*/, short int * /*events*/)
@@ -1123,6 +1197,7 @@ inline POLLINFO *poll_add_fd(POLLJOB *p
1197
1198 p->inf[i].client_ip = NULL;
1199 p->inf[i].client_port = NULL;
1200 + p->inf[i].client_host = NULL;
1201 p->inf[i].del_callback = p->del_callback;
1202 p->inf[i].rcv_callback = p->rcv_callback;
1203 p->inf[i].snd_callback = p->snd_callback;
@@ -1153,8 +1228,9 @@ inline POLLINFO *poll_add_fd(POLLJOB *p
1228 pi->port_acl = port_acl;
1229 pi->flags = flags;
1230 pi->next = NULL;
1156 - pi->client_ip = strdupz(client_ip);
1231 + pi->client_ip = strdupz(client_ip);
1232 pi->client_port = strdupz(client_port);
1233 + pi->client_host = strdupz(client_host);
1234
1235 pi->del_callback = del_callback;
1236 pi->rcv_callback = rcv_callback;
@@ -1356,13 +1432,16 @@ static void poll_events_process(POLLJOB *p, POLLINFO *pi, struct pollfd *pf, sho
1432
1433 int nfd;
1434 do {
1359 - char client_ip[NI_MAXHOST + 1];
1360 - char client_port[NI_MAXSERV + 1];
1361 - client_ip[0] = 0x00;
1362 - client_port[0] = 0x00;
1435 + char client_ip[INET6_ADDRSTRLEN];
1436 + char client_port[NI_MAXSERV];
1437 + char client_host[NI_MAXHOST];
1438 + client_host[0] = 0;
1439 + client_ip[0] = 0;
1440 + client_port[0] = 0;
1441
1442 debug(D_POLLFD, "POLLFD: LISTENER: calling accept4() slot %zu (fd %d)", i, fd);
1365 - nfd = accept_socket(fd, SOCK_NONBLOCK, client_ip, NI_MAXHOST + 1, client_port, NI_MAXSERV + 1, p->access_list);
1443 + nfd = accept_socket(fd, SOCK_NONBLOCK, client_ip, INET6_ADDRSTRLEN, client_port, NI_MAXSERV,
1444 + client_host, NI_MAXHOST, p->access_list);
1445 if (unlikely(nfd < 0)) {
1446 // accept failed
1447
@@ -1387,6 +1466,7 @@ static void poll_events_process(POLLJOB *p, POLLINFO *pi, struct pollfd *pf, sho
1466 , POLLINFO_FLAG_CLIENT_SOCKET
1467 , client_ip
1468 , client_port
1469 + , client_host
1470 , p->add_callback
1471 , p->del_callback
1472 , p->rcv_callback
@@ -1530,6 +1610,7 @@ void poll_events(LISTEN_SOCKETS *sockets
1610 , POLLINFO_FLAG_SERVER_SOCKET
1611 , (sockets->fds_names[i])?sockets->fds_names[i]:"UNKNOWN"
1612 , ""
1613 + , ""
1614 , p.add_callback
1615 , p.del_callback
1616 , p.rcv_callback
libnetdata/socket/socket.h
+8 -3
@@ -72,7 +72,10 @@ extern int sock_setreuse_port(int fd, int reuse);
72 extern int sock_enlarge_in(int fd);
73 extern int sock_enlarge_out(int fd);
74
75 -extern int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *client_port, size_t portsize, SIMPLE_PATTERN *access_list);
75 +extern int connection_allowed(int fd, char *client_ip, char *client_host, size_t hostsize,
76 + SIMPLE_PATTERN *access_list, const char *patname);
77 +extern int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *client_port, size_t portsize,
78 + char *client_host, size_t hostsize, SIMPLE_PATTERN *access_list);
79
80 #ifndef HAVE_ACCEPT4
81 extern int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags);
@@ -104,8 +107,9 @@ typedef struct pollinfo {
107 int fd; // the file descriptor
108 int socktype; // the client socket type
109 WEB_CLIENT_ACL port_acl; // the access lists permitted on this web server port (it's -1 for client sockets)
107 - char *client_ip; // the connected client IP
108 - char *client_port; // the connected client port
110 + char *client_ip; // Max INET6_ADDRSTRLEN bytes
111 + char *client_port; // Max NI_MAXSERV bytes
112 + char *client_host; // Max NI_MAXHOST bytes
113
114 time_t connected_t; // the time the socket connected
115 time_t last_received_t; // the time the socket last received data
@@ -173,6 +177,7 @@ extern POLLINFO *poll_add_fd(POLLJOB *p
177 , uint32_t flags
178 , const char *client_ip
179 , const char *client_port
180 + , const char *client_host
181 , void *(*add_callback)(POLLINFO *pi, short int *events, void *data)
182 , void (*del_callback)(POLLINFO *pi)
183 , int (*rcv_callback)(POLLINFO *pi, short int *events)
web/server/static/static-threaded.c
+17 -14
@@ -7,23 +7,26 @@ int web_client_timeout = DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS;
7 int web_client_first_request_timeout = DEFAULT_TIMEOUT_TO_RECEIVE_FIRST_WEB_REQUEST;
8 long web_client_streaming_rate_t = 0L;
9
10 -// ----------------------------------------------------------------------------
11 -// high level web clients connection management
12 -
13 -static struct web_client *web_client_create_on_fd(int fd, const char *client_ip, const char *client_port, int port_acl) {
10 +/*
11 + * --------------------------------------------------------------------------------------------------------------------
12 + * Build web_client state from the pollinfo that describes an accepted connection.
13 + */
14 +static struct web_client *web_client_create_on_fd(POLLINFO *pi) {
15 struct web_client *w;
16
17 w = web_client_get_from_cache_or_allocate();
17 - w->ifd = w->ofd = fd;
18 + w->ifd = w->ofd = pi->fd;
19
19 - strncpyz(w->client_ip, client_ip, sizeof(w->client_ip) - 1);
20 - strncpyz(w->client_port, client_port, sizeof(w->client_port) - 1);
20 + strncpyz(w->client_ip, pi->client_ip, sizeof(w->client_ip) - 1);
21 + strncpyz(w->client_port, pi->client_port, sizeof(w->client_port) - 1);
22 + strncpyz(w->client_host, pi->client_host, sizeof(w->client_host) - 1);
23
24 if(unlikely(!*w->client_ip)) strcpy(w->client_ip, "-");
25 if(unlikely(!*w->client_port)) strcpy(w->client_port, "-");
24 - w->port_acl = port_acl;
26 + w->port_acl = pi->port_acl;
27
28 web_client_initialize_connection(w);
29 + w->pollinfo_slot = pi->slot;
30 return(w);
31 }
32
@@ -76,7 +79,7 @@ static void *web_server_file_add_callback(POLLINFO *pi, short int *events, void
79 return w;
80 }
81
79 -static void web_werver_file_del_callback(POLLINFO *pi) {
82 +static void web_server_file_del_callback(POLLINFO *pi) {
83 struct web_client *w = (struct web_client *)pi->data;
84 debug(D_WEB_CLIENT, "%llu: RELEASE FILE READ ON FD %d", w->id, pi->fd);
85
@@ -138,7 +141,7 @@ static int web_server_file_write_callback(POLLINFO *pi, short int *events) {
141 // web server clients
142
143 static void *web_server_add_callback(POLLINFO *pi, short int *events, void *data) {
141 - (void)data;
144 + (void)data; // Supress warning on unused argument
145
146 worker_private->connected++;
147
@@ -149,10 +152,9 @@ static void *web_server_add_callback(POLLINFO *pi, short int *events, void *data
152 *events = POLLIN;
153
154 debug(D_WEB_CLIENT_ACCESS, "LISTENER on %d: new connection.", pi->fd);
152 - struct web_client *w = web_client_create_on_fd(pi->fd, pi->client_ip, pi->client_port, pi->port_acl);
153 - w->pollinfo_slot = pi->slot;
155 + struct web_client *w = web_client_create_on_fd(pi);
156
155 - if ( !strncmp(pi->client_port,"UNIX",4)){
157 + if (!strncmp(pi->client_port, "UNIX", 4)) {
158 web_client_set_unix(w);
159 } else {
160 web_client_set_tcp(w);
@@ -270,8 +272,9 @@ static int web_server_rcv_callback(POLLINFO *pi, short int *events) {
272 , POLLINFO_FLAG_CLIENT_SOCKET
273 , "FILENAME"
274 , ""
275 + , ""
276 , web_server_file_add_callback
274 - , web_werver_file_del_callback
277 + , web_server_file_del_callback
278 , web_server_file_read_callback
279 , web_server_file_write_callback
280 , (void *) w
web/server/web_client.c
+3 -3
@@ -791,7 +791,7 @@ static inline char *http_header_parse(struct web_client *w, char *s, int parse_u
791 w->auth_bearer_token = strdupz(v);
792 }
793 else if(hash == hash_host && !strcasecmp(s, "Host")){
794 - strncpyz(w->host, v, ((size_t)(ve - v) < sizeof(w->host)-1 ? (size_t)(ve - v) : sizeof(w->host)-1));
794 + strncpyz(w->server_host, v, ((size_t)(ve - v) < sizeof(w->server_host)-1 ? (size_t)(ve - v) : sizeof(w->server_host)-1));
795 }
796 #ifdef NETDATA_WITH_ZLIB
797 else if(hash == hash_accept_encoding && !strcasecmp(s, "Accept-Encoding")) {
@@ -1147,8 +1147,8 @@ static inline void web_client_send_http_header(struct web_client *w) {
1147 char headerbegin[8328];
1148 if (w->response.code == HTTP_RESP_MOVED_PERM) {
1149 memcpy(headerbegin,"\r\nLocation: https://",20);
1150 - size_t headerlength = strlen(w->host);
1151 - memcpy(&headerbegin[20],w->host,headerlength);
1150 + size_t headerlength = strlen(w->server_host);
1151 + memcpy(&headerbegin[20],w->server_host,headerlength);
1152 headerlength += 20;
1153 size_t tmp = strlen(w->last_url);
1154 memcpy(&headerbegin[headerlength],w->last_url,tmp);
web/server/web_client.h
+4 -3
@@ -154,13 +154,14 @@ struct web_client {
154 int ifd;
155 int ofd;
156
157 - char client_ip[NI_MAXHOST+1];
158 - char client_port[NI_MAXSERV+1];
157 + char client_ip[INET6_ADDRSTRLEN]; // Defined buffer sizes include null-terminators
158 + char client_port[NI_MAXSERV];
159 + char server_host[NI_MAXHOST];
160 + char client_host[NI_MAXHOST];
161
162 char decoded_url[NETDATA_WEB_REQUEST_URL_SIZE + 1]; // we decode the URL in this buffer
163 char decoded_query_string[NETDATA_WEB_REQUEST_URL_SIZE + 1]; // we decode the Query String in this buffer
164 char last_url[NETDATA_WEB_REQUEST_URL_SIZE+1]; // we keep a copy of the decoded URL here
163 - char host[256];
165 size_t url_path_length;
166 char separator; // This value can be either '?' or 'f'
167 char *url_search_path; //A pointer to the search path sent by the client
web/server/web_server.c
+18 -6
@@ -86,22 +86,34 @@ SIMPLE_PATTERN *web_allow_netdataconf_from = NULL;
86 void web_client_update_acl_matches(struct web_client *w) {
87 w->acl = WEB_CLIENT_ACL_NONE;
88
89 - if(!web_allow_dashboard_from || simple_pattern_matches(web_allow_dashboard_from, w->client_ip))
89 + if (!web_allow_dashboard_from ||
90 + connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
91 + web_allow_dashboard_from, "dashboard"))
92 w->acl |= WEB_CLIENT_ACL_DASHBOARD;
93
92 - if(!web_allow_registry_from || simple_pattern_matches(web_allow_registry_from, w->client_ip))
94 + if (!web_allow_registry_from ||
95 + connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
96 + web_allow_registry_from, "registry"))
97 w->acl |= WEB_CLIENT_ACL_REGISTRY;
98
95 - if(!web_allow_badges_from || simple_pattern_matches(web_allow_badges_from, w->client_ip))
99 + if (!web_allow_badges_from ||
100 + connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
101 + web_allow_badges_from, "badges"))
102 w->acl |= WEB_CLIENT_ACL_BADGE;
103
98 - if(!web_allow_mgmt_from || simple_pattern_matches(web_allow_mgmt_from, w->client_ip))
104 + if (!web_allow_mgmt_from ||
105 + connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
106 + web_allow_mgmt_from, "management"))
107 w->acl |= WEB_CLIENT_ACL_MGMT;
108
101 - if(!web_allow_streaming_from || simple_pattern_matches(web_allow_streaming_from, w->client_ip))
109 + if (!web_allow_streaming_from ||
110 + connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
111 + web_allow_streaming_from, "streaming"))
112 w->acl |= WEB_CLIENT_ACL_STREAMING;
113
104 - if(!web_allow_netdataconf_from || simple_pattern_matches(web_allow_netdataconf_from, w->client_ip))
114 + if (!web_allow_netdataconf_from ||
115 + connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
116 + web_allow_netdataconf_from, "netdata.conf"))
117 w->acl |= WEB_CLIENT_ACL_NETDATACONF;
118
119 w->acl &= w->port_acl;