summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source4/lib/socket/access.c353
-rw-r--r--source4/lib/socket/config.mk2
-rw-r--r--source4/lib/socket/socket.c9
-rw-r--r--source4/lib/socket/socket.h1
-rw-r--r--source4/lib/socket/socket_ipv4.c21
-rw-r--r--source4/smb_server/service.c7
-rw-r--r--source4/smbd/service.c5
7 files changed, 398 insertions, 0 deletions
diff --git a/source4/lib/socket/access.c b/source4/lib/socket/access.c
new file mode 100644
index 0000000000..f33f8d56b1
--- /dev/null
+++ b/source4/lib/socket/access.c
@@ -0,0 +1,353 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ check access rules for socket connections
+
+ Copyright (C) Andrew Tridgell 2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+
+/*
+ This module is an adaption of code from the tcpd-1.4 package written
+ by Wietse Venema, Eindhoven University of Technology, The Netherlands.
+
+ The code is used here with permission.
+
+ The code has been considerably changed from the original. Bug reports
+ should be sent to samba@samba.org
+*/
+
+#include "includes.h"
+
+#define FAIL (-1)
+#define ALLONES ((uint32_t)0xFFFFFFFF)
+
+/* masked_match - match address against netnumber/netmask */
+static BOOL masked_match(TALLOC_CTX *mem_ctx, const char *tok, const char *slash, const char *s)
+{
+ uint32_t net;
+ uint32_t mask;
+ uint32_t addr;
+ char *tok_cpy;
+
+ if ((addr = interpret_addr(s)) == INADDR_NONE)
+ return False;
+
+ tok_cpy = talloc_strdup(mem_ctx, tok);
+ tok_cpy[PTR_DIFF(slash,tok)] = '\0';
+ net = interpret_addr(tok_cpy);
+ talloc_free(tok_cpy);
+
+ if (strlen(slash + 1) > 2) {
+ mask = interpret_addr(slash + 1);
+ } else {
+ mask = (uint32_t)((ALLONES >> atoi(slash + 1)) ^ ALLONES);
+ /* convert to network byte order */
+ mask = htonl(mask);
+ }
+
+ if (net == INADDR_NONE || mask == INADDR_NONE) {
+ DEBUG(0,("access: bad net/mask access control: %s\n", tok));
+ return False;
+ }
+
+ return (addr & mask) == (net & mask);
+}
+
+/* string_match - match string against token */
+static BOOL string_match(TALLOC_CTX *mem_ctx, const char *tok,const char *s, char *invalid_char)
+{
+ size_t tok_len;
+ size_t str_len;
+ const char *cut;
+
+ *invalid_char = '\0';
+
+ /* Return True if a token has the magic value "ALL". Return
+ * FAIL if the token is "FAIL". If the token starts with a "."
+ * (domain name), return True if it matches the last fields of
+ * the string. If the token has the magic value "LOCAL",
+ * return True if the string does not contain a "."
+ * character. If the token ends on a "." (network number),
+ * return True if it matches the first fields of the
+ * string. If the token begins with a "@" (netgroup name),
+ * return True if the string is a (host) member of the
+ * netgroup. Return True if the token fully matches the
+ * string. If the token is a netnumber/netmask pair, return
+ * True if the address is a member of the specified subnet.
+ */
+
+ if (tok[0] == '.') { /* domain: match last fields */
+ if ((str_len = strlen(s)) > (tok_len = strlen(tok))
+ && strcasecmp(tok, s + str_len - tok_len)==0) {
+ return True;
+ }
+ } else if (tok[0] == '@') { /* netgroup: look it up */
+ DEBUG(0,("access: netgroup support is not available\n"));
+ return False;
+ } else if (strcmp(tok, "ALL")==0) { /* all: match any */
+ return True;
+ } else if (strcmp(tok, "FAIL")==0) { /* fail: match any */
+ return FAIL;
+ } else if (strcmp(tok, "LOCAL")==0) { /* local: no dots */
+ if (strchr(s, '.') == 0 && strcasecmp(s, "unknown") != 0) {
+ return True;
+ }
+ } else if (strcasecmp(tok, s)==0) { /* match host name or address */
+ return True;
+ } else if (tok[(tok_len = strlen(tok)) - 1] == '.') { /* network */
+ if (strncmp(tok, s, tok_len) == 0)
+ return True;
+ } else if ((cut = strchr(tok, '/')) != 0) { /* netnumber/netmask */
+ if (isdigit((int)s[0]) && masked_match(mem_ctx, tok, cut, s))
+ return True;
+ } else if (strchr(tok, '*') != 0) {
+ *invalid_char = '*';
+ } else if (strchr(tok, '?') != 0) {
+ *invalid_char = '?';
+ }
+ return False;
+}
+
+struct client_addr {
+ const char *cname;
+ const char *caddr;
+};
+
+/* client_match - match host name and address against token */
+static BOOL client_match(TALLOC_CTX *mem_ctx, const char *tok, struct client_addr *client)
+{
+ BOOL match;
+ char invalid_char = '\0';
+
+ /*
+ * Try to match the address first. If that fails, try to match the host
+ * name if available.
+ */
+
+ if ((match = string_match(mem_ctx, tok, client->caddr, &invalid_char)) == 0) {
+ if(invalid_char)
+ DEBUG(0,("client_match: address match failing due to invalid character '%c' found in \
+token '%s' in an allow/deny hosts line.\n", invalid_char, tok ));
+
+ if (client->cname[0] != 0)
+ match = string_match(mem_ctx, tok, client->cname, &invalid_char);
+
+ if(invalid_char)
+ DEBUG(0,("client_match: address match failing due to invalid character '%c' found in \
+token '%s' in an allow/deny hosts line.\n", invalid_char, tok ));
+ }
+
+ return (match);
+}
+
+/* list_match - match an item against a list of tokens with exceptions */
+static BOOL list_match(TALLOC_CTX *mem_ctx, const char **list, struct client_addr *client)
+{
+ BOOL match = False;
+
+ if (!list)
+ return False;
+
+ /*
+ * Process tokens one at a time. We have exhausted all possible matches
+ * when we reach an "EXCEPT" token or the end of the list. If we do find
+ * a match, look for an "EXCEPT" list and recurse to determine whether
+ * the match is affected by any exceptions.
+ */
+
+ for (; *list ; list++) {
+ if (strcmp(*list, "EXCEPT")==0) /* EXCEPT: give up */
+ break;
+ if ((match = client_match(mem_ctx, *list, client))) /* True or FAIL */
+ break;
+ }
+
+ /* Process exceptions to True or FAIL matches. */
+ if (match != False) {
+ while (*list && strcmp(*list, "EXCEPT")!=0)
+ list++;
+
+ for (; *list; list++) {
+ if (client_match(mem_ctx, *list, client)) /* Exception Found */
+ return False;
+ }
+ }
+
+ return match;
+}
+
+/* return true if access should be allowed */
+static BOOL allow_access_internal(TALLOC_CTX *mem_ctx,
+ const char **deny_list,const char **allow_list,
+ const char *cname, const char *caddr)
+{
+ struct client_addr client;
+
+ client.cname = cname;
+ client.caddr = caddr;
+
+ /* if it is loopback then always allow unless specifically denied */
+ if (strcmp(caddr, "127.0.0.1") == 0) {
+ /*
+ * If 127.0.0.1 matches both allow and deny then allow.
+ * Patch from Steve Langasek vorlon@netexpress.net.
+ */
+ if (deny_list &&
+ list_match(mem_ctx, deny_list, &client) &&
+ (!allow_list ||
+ !list_match(mem_ctx, allow_list, &client))) {
+ return False;
+ }
+ return True;
+ }
+
+ /* if theres no deny list and no allow list then allow access */
+ if ((!deny_list || *deny_list == 0) &&
+ (!allow_list || *allow_list == 0)) {
+ return True;
+ }
+
+ /* if there is an allow list but no deny list then allow only hosts
+ on the allow list */
+ if (!deny_list || *deny_list == 0)
+ return list_match(mem_ctx, allow_list, &client);
+
+ /* if theres a deny list but no allow list then allow
+ all hosts not on the deny list */
+ if (!allow_list || *allow_list == 0)
+ return !list_match(mem_ctx, deny_list, &client);
+
+ /* if there are both types of list then allow all hosts on the
+ allow list */
+ if (list_match(mem_ctx, allow_list, &client))
+ return True;
+
+ /* if there are both types of list and it's not on the allow then
+ allow it if its not on the deny */
+ if (list_match(mem_ctx, deny_list, &client))
+ return False;
+
+ return True;
+}
+
+/* return true if access should be allowed */
+static BOOL allow_access(TALLOC_CTX *mem_ctx,
+ const char **deny_list, const char **allow_list,
+ const char *cname, const char *caddr)
+{
+ BOOL ret;
+ char *nc_cname = talloc_strdup(mem_ctx, cname);
+ char *nc_caddr = talloc_strdup(mem_ctx, caddr);
+
+ if (!nc_cname || !nc_caddr) {
+ return False;
+ }
+
+ ret = allow_access_internal(mem_ctx, deny_list, allow_list, nc_cname, nc_caddr);
+
+ talloc_free(nc_cname);
+ talloc_free(nc_caddr);
+
+ return ret;
+}
+
+/* return true if the char* contains ip addrs only. Used to avoid
+gethostbyaddr() calls */
+
+static BOOL only_ipaddrs_in_list(const char** list)
+{
+ BOOL only_ip = True;
+
+ if (!list)
+ return True;
+
+ for (; *list ; list++) {
+ /* factor out the special strings */
+ if (strcmp(*list, "ALL")==0 ||
+ strcmp(*list, "FAIL")==0 ||
+ strcmp(*list, "EXCEPT")==0) {
+ continue;
+ }
+
+ if (!is_ipaddress(*list)) {
+ /*
+ * if we failed, make sure that it was not because the token
+ * was a network/netmask pair. Only network/netmask pairs
+ * have a '/' in them
+ */
+ if ((strchr(*list, '/')) == NULL) {
+ only_ip = False;
+ DEBUG(3,("only_ipaddrs_in_list: list has non-ip address (%s)\n", *list));
+ break;
+ }
+ }
+ }
+
+ return only_ip;
+}
+
+/* return true if access should be allowed to a service for a socket */
+BOOL socket_check_access(struct socket_context *sock,
+ const char *service_name,
+ const char **allow_list, const char **deny_list)
+{
+ BOOL ret;
+ const char *name="", *addr;
+ TALLOC_CTX *mem_ctx;
+
+ if ((!deny_list || *deny_list==0) &&
+ (!allow_list || *allow_list==0)) {
+ return True;
+ }
+
+ mem_ctx = talloc_init("socket_check_access");
+ if (!mem_ctx) {
+ return False;
+ }
+
+ addr = socket_get_peer_addr(sock, mem_ctx);
+
+ /* bypass gethostbyaddr() calls if the lists only contain IP addrs */
+ if (!only_ipaddrs_in_list(allow_list) ||
+ !only_ipaddrs_in_list(deny_list)) {
+ name = socket_get_peer_name(sock, mem_ctx);
+ if (!name) {
+ name = addr;
+ }
+ }
+
+ if (!addr) {
+ DEBUG(0,("socket_check_access: Denied connection from unknown host\n"));
+ talloc_free(mem_ctx);
+ return False;
+ }
+
+ ret = allow_access(mem_ctx, deny_list, allow_list, name, addr);
+
+ if (ret) {
+ DEBUG(2,("socket_check_access: Allowed connection to '%s' from %s (%s)\n",
+ service_name, name, addr));
+ } else {
+ DEBUG(0,("socket_check_access: Denied connection to '%s' from %s (%s)\n",
+ service_name, name, addr));
+ }
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
diff --git a/source4/lib/socket/config.mk b/source4/lib/socket/config.mk
index 854702a6f8..320fc7f3ee 100644
--- a/source4/lib/socket/config.mk
+++ b/source4/lib/socket/config.mk
@@ -12,5 +12,7 @@ INIT_OBJ_FILES = \
[SUBSYSTEM::SOCKET]
INIT_OBJ_FILES = \
lib/socket/socket.o
+ADD_OBJ_FILES = \
+ lib/socket/access.o
# End SUBSYSTEM SOCKET
################################################
diff --git a/source4/lib/socket/socket.c b/source4/lib/socket/socket.c
index 6869114587..4814cc6a5a 100644
--- a/source4/lib/socket/socket.c
+++ b/source4/lib/socket/socket.c
@@ -160,6 +160,15 @@ NTSTATUS socket_set_option(struct socket_context *sock, const char *option, cons
return sock->ops->set_option(sock, option, val);
}
+char *socket_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx)
+{
+ if (!sock->ops->get_peer_name) {
+ return NULL;
+ }
+
+ return sock->ops->get_peer_name(sock, mem_ctx);
+}
+
char *socket_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx)
{
if (!sock->ops->get_peer_addr) {
diff --git a/source4/lib/socket/socket.h b/source4/lib/socket/socket.h
index ea4de1d291..a089a1b78a 100644
--- a/source4/lib/socket/socket.h
+++ b/source4/lib/socket/socket.h
@@ -55,6 +55,7 @@ struct socket_ops {
NTSTATUS (*set_option)(struct socket_context *sock, const char *option, const char *val);
+ char *(*get_peer_name)(struct socket_context *sock, TALLOC_CTX *mem_ctx);
char *(*get_peer_addr)(struct socket_context *sock, TALLOC_CTX *mem_ctx);
int (*get_peer_port)(struct socket_context *sock);
char *(*get_my_addr)(struct socket_context *sock, TALLOC_CTX *mem_ctx);
diff --git a/source4/lib/socket/socket_ipv4.c b/source4/lib/socket/socket_ipv4.c
index 9a1e7e43f6..151c49518f 100644
--- a/source4/lib/socket/socket_ipv4.c
+++ b/source4/lib/socket/socket_ipv4.c
@@ -293,6 +293,26 @@ static NTSTATUS ipv4_tcp_set_option(struct socket_context *sock, const char *opt
return NT_STATUS_OK;
}
+static char *ipv4_tcp_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx)
+{
+ struct sockaddr_in peer_addr;
+ socklen_t len = sizeof(peer_addr);
+ struct hostent *he;
+ int ret;
+
+ ret = getpeername(sock->fd, (struct sockaddr *)&peer_addr, &len);
+ if (ret == -1) {
+ return NULL;
+ }
+
+ he = gethostbyaddr((char *)&peer_addr.sin_addr, sizeof(peer_addr.sin_addr), AF_INET);
+ if (he == NULL) {
+ return NULL;
+ }
+
+ return talloc_strdup(mem_ctx, he->h_name);
+}
+
static char *ipv4_tcp_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx)
{
struct sockaddr_in peer_addr;
@@ -368,6 +388,7 @@ static const struct socket_ops ipv4_tcp_ops = {
.set_option = ipv4_tcp_set_option,
+ .get_peer_name = ipv4_tcp_get_peer_name,
.get_peer_addr = ipv4_tcp_get_peer_addr,
.get_peer_port = ipv4_tcp_get_peer_port,
.get_my_addr = ipv4_tcp_get_my_addr,
diff --git a/source4/smb_server/service.c b/source4/smb_server/service.c
index bc436172a1..5f698fe20b 100644
--- a/source4/smb_server/service.c
+++ b/source4/smb_server/service.c
@@ -143,6 +143,13 @@ static NTSTATUS make_connection_snum(struct smbsrv_request *req,
struct smbsrv_tcon *tcon;
NTSTATUS status;
+ if (!socket_check_access(req->smb_conn->connection->socket,
+ lp_servicename(snum),
+ lp_hostsallow(snum),
+ lp_hostsdeny(snum))) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
tcon = conn_new(req->smb_conn);
if (!tcon) {
DEBUG(0,("Couldn't find free connection.\n"));
diff --git a/source4/smbd/service.c b/source4/smbd/service.c
index 323bc62a85..278a763657 100644
--- a/source4/smbd/service.c
+++ b/source4/smbd/service.c
@@ -226,6 +226,11 @@ struct server_connection *server_setup_connection(struct event_context *ev, stru
srv_conn->event.fde = event_add_fd(ev,&fde);
srv_conn->event.idle = event_add_timed(ev,&idle);
+ if (!socket_check_access(sock, "smbd", lp_hostsallow(-1), lp_hostsdeny(-1))) {
+ server_terminate_connection(srv_conn, "denied by access rules");
+ return NULL;
+ }
+
return srv_conn;
}