diff options
-rw-r--r-- | source4/lib/socket/access.c | 353 | ||||
-rw-r--r-- | source4/lib/socket/config.mk | 2 | ||||
-rw-r--r-- | source4/lib/socket/socket.c | 9 | ||||
-rw-r--r-- | source4/lib/socket/socket.h | 1 | ||||
-rw-r--r-- | source4/lib/socket/socket_ipv4.c | 21 | ||||
-rw-r--r-- | source4/smb_server/service.c | 7 | ||||
-rw-r--r-- | source4/smbd/service.c | 5 |
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; } |