ᴄʜʀɪsᴛᴏᴘʜᴇʀ ᴍᴇɴɢAdd IDN support (#986)f0177d82 months agoHistory
317 lines · 7.0 KB · C
RawDownload
/* * Routines to authenticate access to a daemon (hosts allow/deny). * * Copyright (C) 1998 Andrew Tridgell * Copyright (C) 2004-2022 Wayne Davison * * 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 3 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, visit the http://fsf.org website. */ #include "rsync.h"#include "ifuncs.h"#ifdef HAVE_NETGROUP_H#include <netgroup.h>#endif static int allow_forward_dns; extern const char undetermined_hostname[]; static int match_hostname(const char **host_ptr, const char *addr, const char *tok, int deny){	struct hostent *hp;	unsigned int i;	const char *host = *host_ptr;#ifdef SUPPORT_IDN	char idn_tok[1024];#endif 	if (!host || !*host)		return 0; #ifdef HAVE_INNETGR	if (*tok == '@' && tok[1])		return innetgr(tok + 1, host, NULL, NULL);#endif #ifdef SUPPORT_IDN	/* A hostname reaches us from DNS as ASCII, so fold an IDN token to its	 * A-label form before comparing.  An all-ASCII token, and a token we	 * can't fold, are both left as they are. */	if (idn_to_ascii(tok, 0, idn_tok, sizeof idn_tok))		tok = idn_tok;#endif 	/* First check if the reverse-DNS-determined hostname matches. */	if (iwildmatch(tok, host))		return 1; 	if (!allow_forward_dns)		return 0; 	/* Fail quietly if tok is an address or wildcarded entry, not a simple hostname. */	if (!tok[strspn(tok, ".0123456789")] || tok[strcspn(tok, ":/*?[")])		return 0; 	/* Now try forward-DNS on the token (config-specified hostname) and see if the IP matches. */	if (!(hp = gethostbyname(tok))) {		/* A deny-list hostname token we cannot resolve must fail CLOSED:		 * we can't prove the peer isn't the denied host, so treat the		 * unresolvable token as a match (deny).  Allow-list tokens keep		 * failing as a non-match.  Sibling of CVE-2026-43617, which fixed		 * only the reverse-lookup path. */		return deny;	} 	for (i = 0; hp->h_addr_list[i] != NULL; i++) {		if (strcmp(addr, inet_ntoa(*(struct in_addr*)(hp->h_addr_list[i]))) == 0) {			/* If reverse lookups are off, we'll use the conf-specified			 * hostname in preference to UNDETERMINED. */			if (host == undetermined_hostname)				*host_ptr = strdup(tok);			return 1;		}	} 	return 0;} static int match_binary(const char *b1, const char *b2, const char *mask, int addrlen){	int i; 	for (i = 0; i < addrlen; i++) {		if ((b1[i] ^ b2[i]) & mask[i])			return 0;	} 	return 1;} static void make_mask(char *mask, int plen, int addrlen){	int w, b; 	w = plen >> 3;	b = plen & 0x7; 	if (w)		memset(mask, 0xff, w);	if (w < addrlen)		mask[w] = 0xff & (0xff<<(8-b));	if (w+1 < addrlen)		memset(mask+w+1, 0, addrlen-w-1); 	return;} static int match_address(const char *addr, char *tok){	char *p;	struct addrinfo hints, *resa, *rest;	int gai;	int ret = 0;	int addrlen = 0;#ifdef HAVE_STRTOL	long int bits;#else	int bits;#endif	char mask[16];	char *a = NULL, *t = NULL; 	if (!addr || !*addr)		return 0; 	p = strchr(tok,'/');	if (p)		*p = '\0'; 	/* Fail quietly if tok is a hostname, not an address. */	if (tok[strspn(tok, ".0123456789")] && strchr(tok, ':') == NULL) {		if (p)			*p = '/';		return 0;	} 	memset(&hints, 0, sizeof(hints));	hints.ai_family = PF_UNSPEC;	hints.ai_socktype = SOCK_STREAM;#ifdef AI_NUMERICHOST	hints.ai_flags = AI_NUMERICHOST;#endif 	if (getaddrinfo(addr, NULL, &hints, &resa) != 0) {		if (p)			*p = '/';		return 0;	} 	gai = getaddrinfo(tok, NULL, &hints, &rest);	if (p)		*p++ = '/';	if (gai != 0) {		rprintf(FLOG, "error matching address %s: %s\n",			tok, gai_strerror(gai));		freeaddrinfo(resa);		return 0;	} 	if (rest->ai_family != resa->ai_family) {		ret = 0;		goto out;	} 	switch(resa->ai_family) {	case PF_INET:		a = (char *)&((struct sockaddr_in *)resa->ai_addr)->sin_addr;		t = (char *)&((struct sockaddr_in *)rest->ai_addr)->sin_addr;		addrlen = 4; 		break; #ifdef INET6	case PF_INET6: {		struct sockaddr_in6 *sin6a, *sin6t; 		sin6a = (struct sockaddr_in6 *)resa->ai_addr;		sin6t = (struct sockaddr_in6 *)rest->ai_addr; 		a = (char *)&sin6a->sin6_addr;		t = (char *)&sin6t->sin6_addr; 		addrlen = 16; #ifdef HAVE_SOCKADDR_IN6_SCOPE_ID		if (sin6t->sin6_scope_id && sin6a->sin6_scope_id != sin6t->sin6_scope_id) {			ret = 0;			goto out;		}#endif 		break;	}#endif	default:		rprintf(FLOG, "unknown family %u\n", rest->ai_family);		ret = 0;		goto out;	} 	bits = -1;	if (p) {		if (inet_pton(resa->ai_addr->sa_family, p, mask) <= 0) {#ifdef HAVE_STRTOL			char *ep = NULL;#else			unsigned char *pp;#endif #ifdef HAVE_STRTOL			bits = strtol(p, &ep, 10);			if (!*p || *ep) {				rprintf(FLOG, "malformed mask in %s\n", tok);				ret = 0;				goto out;			}#else			for (pp = (unsigned char *)p; *pp; pp++) {				if (!isascii(*pp) || !isdigit(*pp)) {					rprintf(FLOG, "malformed mask in %s\n", tok);					ret = 0;					goto out;				}			}			bits = atoi(p);#endif			if (bits == 0) {				ret = 1;				goto out;			}			if (bits < 0 || bits > (addrlen << 3)) {				rprintf(FLOG, "malformed mask in %s\n", tok);				ret = 0;				goto out;			}		}	} else {		bits = 128;	} 	if (bits >= 0)		make_mask(mask, bits, addrlen); 	ret = match_binary(a, t, mask, addrlen);   out:	freeaddrinfo(resa);	freeaddrinfo(rest);	return ret;} static int access_match(const char *list, const char *addr, const char **host_ptr, int deny){	char *tok;	char *list2 = strdup(list); 	strlower(list2); 	for (tok = strtok(list2, " ,\t"); tok; tok = strtok(NULL, " ,\t")) {		if (match_hostname(host_ptr, addr, tok, deny) || match_address(addr, tok)) {			free(list2);			return 1;		}	} 	free(list2);	return 0;} int allow_access(const char *addr, const char **host_ptr, int i){	const char *allow_list = lp_hosts_allow(i);	const char *deny_list = lp_hosts_deny(i); 	if (allow_list && !*allow_list)		allow_list = NULL;	if (deny_list && !*deny_list)		deny_list = NULL; 	allow_forward_dns = lp_forward_lookup(i); 	/* If we match an allow-list item, we always allow access. */	if (allow_list) {		if (access_match(allow_list, addr, host_ptr, 0))			return 1;		/* For an allow-list w/o a deny-list, disallow non-matches. */		if (!deny_list)			return 0;	} 	/* If we match a deny-list item (and got past any allow-list	 * items), we always disallow access. */	if (deny_list && access_match(deny_list, addr, host_ptr, 1))		return 0; 	/* Allow all other access. */	return 1;} int allow_proxy_protocol_peer(const char *list, const char *addr, const char **host_ptr){	if (!list || !*list)		return 0;	allow_forward_dns = 0;	return access_match(list, addr, host_ptr, 0);}