mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-28 13:55:48 +08:00
resolver: the system's name servers and hosts file by default
Without nserver (or fakeresolve) names were resolved by the C library. 3proxy's own resolver is now the default, with the system's name servers: up to 3 from /etc/resolv.conf, on Windows from the registry (set for the system, set for an interface with an address, Tcpip6 interfaces, then those DHCP gave). With none found, the C library as before. nserver takes up to 3 as well (MAXNSERVERS), a fourth is warned about. The name servers in force belong to the process, not a configuration: nserver lines are kept aside while a configuration is read and replace them only once it is committed, so a reload resolves with those in force until then. Resolutions and dnspr read a copy of them. The system's are read at start, before the configuration and a chroot in it, and are in force while it is read, so its host names resolve with them; read again at a commit, and checked every 5 seconds from the main loop while the default is in force. What cannot be read then (within a chroot) stays as last read. The hosts file, read with them, is imported into tables of its own while the default is in force: its first address of a family for a name. A name is looked up as one nsrecord made first, then the hosts file, then the name cache, then name servers. A changed file replaces what was imported. No name cache is made for it: without nscache nothing is cached, as before. The name cache's keys are host names of any case now: DNS names are. Tests: sysdns, a name of the hosts file answered by dnspr without nserver, in any case, and not with one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8517a5675b
commit
d5e78a0900
@ -500,14 +500,28 @@ experimental.</p>
|
||||
|
||||
<p style="margin-left:9%; margin-top: 1em"><b>nserver</b>
|
||||
<i><ipaddr></i>[:<i>port</i>][/<i>tcp</i>] <br>
|
||||
Nameserver to use for name resolutions. If none specified
|
||||
system routines for name resolution is used. Optional port
|
||||
number may be specified. If optional /tcp is added to IP
|
||||
Nameserver to use for name resolutions; up to 3, asked in
|
||||
order. Optional port number may be specified. If optional /tcp is added to IP
|
||||
address, name resolution is performed over TCP. An IPv6 address
|
||||
has to be enclosed in square brackets: <br>
|
||||
<b>nserver 1.1.1.1</b> <br>
|
||||
<b>nserver [2001:4860:4860::8844]</b> <br>
|
||||
<b>nserver [2001:4860:4860::8844]:5353/tcp</b></p>
|
||||
<b>nserver [2001:4860:4860::8844]:5353/tcp</b> <br>
|
||||
If none is specified (and there is no <b>fakeresolve</b>),
|
||||
the system's name servers are used: up to 3 from
|
||||
<i>/etc/resolv.conf</i>, on Windows from the registry (those
|
||||
set for the system, those set for an interface, then those
|
||||
DHCP gave). Names in the hosts file (<i>/etc/hosts</i>, on
|
||||
Windows <i>%SystemRoot%\System32\drivers\etc\hosts</i>)
|
||||
are answered from it, before them, and names in the
|
||||
configuration are resolved this way too. Both are read
|
||||
before the configuration, so before a <b>chroot</b>, and
|
||||
checked for changes every 5 seconds; what cannot be read
|
||||
later (within a <b>chroot</b>) stays as last read. If no
|
||||
name server is found, the system routines for name
|
||||
resolution are used. The <b>nserver</b> lines of a
|
||||
configuration take effect when it is loaded: during a
|
||||
reload the name servers in force are used.</p>
|
||||
|
||||
|
||||
<p style="margin-left:9%; margin-top: 1em"><b>authnserver</b>
|
||||
@ -533,9 +547,11 @@ rather than per-service.</p>
|
||||
Adds static record to nscache. <b>nscache</b> must be
|
||||
enabled and must come first, because the record is placed in
|
||||
the table it allocates - <b>nscache6</b> for a record naming
|
||||
an IPv6 address - and <b>nserver</b> must be set as well:
|
||||
without it the system resolver is used and static records
|
||||
are never consulted. If 0.0.0.0 is used as a hostaddr host
|
||||
an IPv6 address - and names must be resolved by name servers
|
||||
(<b>nserver</b>, or the system's ones by default): with the
|
||||
system routines for name resolution static records are never
|
||||
consulted. A static record takes precedence over the hosts
|
||||
file. If 0.0.0.0 is used as a hostaddr host
|
||||
will never resolve, it can be used to blacklist something or
|
||||
together with <b>dialer</b> command to set up UDL for
|
||||
dialing.</p>
|
||||
|
||||
@ -543,9 +543,8 @@ Use \fBauthcache\fR to speedup authentication. RADIUS feature is currently exper
|
||||
.BR nserver
|
||||
\fI<ipaddr>\fR[:\fIport\fR][/\fItcp\fR]
|
||||
.br
|
||||
Nameserver to use for name resolutions. If none specified
|
||||
system routines for name resolution is
|
||||
used. Optional port number may be specified.
|
||||
Nameserver to use for name resolutions; up to 3, asked in order.
|
||||
Optional port number may be specified.
|
||||
If optional /tcp is added to IP address, name resolution is
|
||||
performed over TCP.
|
||||
An IPv6 address has to be enclosed in square brackets:
|
||||
@ -555,6 +554,18 @@ An IPv6 address has to be enclosed in square brackets:
|
||||
\fBnserver [2001:4860:4860::8844]\fR
|
||||
.br
|
||||
\fBnserver [2001:4860:4860::8844]:5353/tcp\fR
|
||||
.br
|
||||
If none is specified (and there is no \fBfakeresolve\fR), the system's name
|
||||
servers are used: up to 3 from \fI/etc/resolv.conf\fR, on Windows from the
|
||||
registry (those set for the system, those set for an interface, then those
|
||||
DHCP gave). Names in the hosts file (\fI/etc/hosts\fR, on Windows
|
||||
\fI%SystemRoot%\\System32\\drivers\\etc\\hosts\fR) are answered from it,
|
||||
before them, and names in the configuration are resolved this way too. Both
|
||||
are read before the configuration, so before a \fBchroot\fR, and checked for
|
||||
changes every 5 seconds; what cannot be read later (within a \fBchroot\fR)
|
||||
stays as last read. If no name server is found, the system routines for name
|
||||
resolution are used. The \fBnserver\fR lines of a configuration take effect
|
||||
when it is loaded: during a reload the name servers in force are used.
|
||||
|
||||
.br
|
||||
.BR authnserver
|
||||
@ -583,8 +594,9 @@ caches are global rather than per-service.
|
||||
Adds static record to nscache. \fBnscache\fR must be enabled and must come
|
||||
first, because the record is placed in the table it allocates - \fBnscache6\fR
|
||||
for a record naming an IPv6 address - and
|
||||
\fBnserver\fR must be set as well: without it the system resolver is used and
|
||||
static records are never consulted. If 0.0.0.0
|
||||
names must be resolved by name servers (\fBnserver\fR, or the system's
|
||||
ones by default): with the system routines for name resolution static records
|
||||
are never consulted. A static record takes precedence over the hosts file. If 0.0.0.0
|
||||
is used as a hostaddr host will never resolve, it can be used to
|
||||
blacklist something.
|
||||
|
||||
|
||||
@ -293,6 +293,7 @@ void cyclestep(void){
|
||||
needreload = 0;
|
||||
}
|
||||
doschedule();
|
||||
sysdnscheck();
|
||||
if(conf->stdlog)fflush(conf->stdlog);
|
||||
if(timechanged(minutecounter, curtime, MINUTELY)) {
|
||||
struct filemon *fm;
|
||||
@ -555,6 +556,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
||||
#ifdef __linux__
|
||||
nsopen(); /* before a chroot in the configuration takes /proc away */
|
||||
#endif
|
||||
sysdnsinit(); /* the system's name servers and hosts, before a chroot too */
|
||||
_3proxy_mutex_init(&config_mutex);
|
||||
_3proxy_mutex_init(&confref_mutex);
|
||||
_3proxy_mutex_init(&listener_mutex);
|
||||
@ -592,6 +594,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
||||
/* numbered before it is read, as a reload numbers the new one */
|
||||
conf->version++;
|
||||
res = readconfig(fp);
|
||||
if(!res) confdnscommit(conf);
|
||||
resolvfunc = conf->resolvfunc;
|
||||
|
||||
/* readconfig() names the failing line, which an exit status would cut
|
||||
|
||||
41
src/conf.c
41
src/conf.c
@ -251,6 +251,8 @@ static int startservice(void *service){
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int noresolver(void);
|
||||
|
||||
static int h_proxy(int argc, unsigned char ** argv){
|
||||
struct child ch;
|
||||
|
||||
@ -267,7 +269,7 @@ static int h_proxy(int argc, unsigned char ** argv){
|
||||
childdef.helpmessage = " -Xftp - fetch ftp:// URLs\n";
|
||||
#endif
|
||||
#ifdef NOIPV6
|
||||
if(!conf->resolvfunc || (conf->resolvfunc == myresolver && !dns_table.poolsize)){
|
||||
if(noresolver()){
|
||||
fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, proxy may run very slow\n", linenum);
|
||||
}
|
||||
#endif
|
||||
@ -306,7 +308,7 @@ static int h_proxy(int argc, unsigned char ** argv){
|
||||
childdef.isudp = 0;
|
||||
childdef.service = S_SOCKS;
|
||||
#ifdef NOIPV6
|
||||
if(!conf->resolvfunc || (conf->resolvfunc == myresolver && !dns_table.poolsize)){
|
||||
if(noresolver()){
|
||||
fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, socks may run very slow\n", linenum);
|
||||
}
|
||||
#endif
|
||||
@ -374,7 +376,7 @@ static int h_proxy(int argc, unsigned char ** argv){
|
||||
childdef.service = S_DNSPR;
|
||||
childdef.helpmessage = " -s - simple DNS forwarding - do not use 3proxy resolver / name cache\n -Fip - fake: answer all A queries with this IP\n";
|
||||
#ifndef NOIPV6
|
||||
if(!conf->resolvfunc || (conf->resolvfunc == myresolver && !dns_table.poolsize) || conf->resolvfunc == fakeresolver){
|
||||
if(noresolver() || conf->resolvfunc == fakeresolver){
|
||||
fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, dnspr will not work as expected\n", linenum);
|
||||
}
|
||||
#endif
|
||||
@ -735,25 +737,43 @@ static int h_flushusers(int argc, unsigned char **argv){
|
||||
}
|
||||
*/
|
||||
|
||||
/* The name servers of the configuration being read: they replace those in
|
||||
force (the process has one list) once it is committed */
|
||||
static struct nserver cfgns[MAXNSERVERS];
|
||||
static int cfgnsn = 0;
|
||||
|
||||
static int h_nserver(int argc, unsigned char **argv){
|
||||
char *str;
|
||||
|
||||
if(conf->numservers < MAXNSERVERS) {
|
||||
if(cfgnsn < MAXNSERVERS) {
|
||||
memset(cfgns + cfgnsn, 0, sizeof(*cfgns));
|
||||
if((str = strchr((char *)argv[1], '/')))
|
||||
*str = 0;
|
||||
*SAPORT(&conf->nservers[conf->numservers].addr) = htons(53);
|
||||
if(parsehost(46, argv[1], (struct sockaddr *)&conf->nservers[conf->numservers].addr)) return 1;
|
||||
*SAPORT(&cfgns[cfgnsn].addr) = htons(53);
|
||||
if(parsehost(46, argv[1], (struct sockaddr *)&cfgns[cfgnsn].addr)) return 1;
|
||||
if(str) {
|
||||
conf->nservers[conf->numservers].usetcp = strstr(str + 1, "tcp")? 1:0;
|
||||
cfgns[cfgnsn].usetcp = strstr(str + 1, "tcp")? 1:0;
|
||||
*str = '/';
|
||||
}
|
||||
conf->numservers++;
|
||||
|
||||
cfgnsn++;
|
||||
}
|
||||
else fprintf(stderr, "[line %d] Warning: only %d name servers are used, nserver ignored\n", linenum, MAXNSERVERS);
|
||||
conf->resolvfunc = myresolver;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The configuration's name servers, or the system's in their place */
|
||||
void confdnscommit(struct extparam *c){
|
||||
sysdnsapply(c, cfgns, cfgnsn);
|
||||
}
|
||||
|
||||
/* Whether names would be resolved slowly: by the C library, or by name
|
||||
servers, the system's taken by default, without a cache */
|
||||
static int noresolver(void){
|
||||
if(!conf->resolvfunc && !sysdnscount()) return 1;
|
||||
return (!conf->resolvfunc || conf->resolvfunc == myresolver) && !dns_table.poolsize;
|
||||
}
|
||||
|
||||
static int h_authnserver(int argc, unsigned char **argv){
|
||||
char *str;
|
||||
|
||||
@ -2513,6 +2533,8 @@ void freeconf(struct extparam *confp){
|
||||
void confbegin(void){
|
||||
char * args[] = {"auth", "iponly", NULL};
|
||||
|
||||
cfgnsn = 0;
|
||||
|
||||
h_auth(2, (unsigned char **)args);
|
||||
}
|
||||
|
||||
@ -2643,6 +2665,7 @@ int reload (void){
|
||||
_3proxy_mutex_lock(&confref_mutex);
|
||||
runconf = new;
|
||||
_3proxy_mutex_unlock(&confref_mutex);
|
||||
confdnscommit(new);
|
||||
resolvfunc = new->resolvfunc;
|
||||
/* the new services take over the listening sockets of the old ones on
|
||||
the same addresses, which then stop accepting, and start */
|
||||
|
||||
17
src/dnspr.c
17
src/dnspr.c
@ -146,19 +146,20 @@ static int dnsreply(struct udploop *u, struct udpsession *ss, unsigned char *buf
|
||||
static int dnsforward(struct udploop *u, struct udpsession *ss){
|
||||
struct clientparam *param = ss->param;
|
||||
struct dnsquery *q = ss->data;
|
||||
struct extparam *c = param->srv->config;
|
||||
struct nserver ns[MAXNSERVERS];
|
||||
int n = getnservers(ns);
|
||||
#ifdef _WIN32
|
||||
unsigned long ul = 1;
|
||||
#endif
|
||||
|
||||
for(; q->server < c->numservers; q->server++){
|
||||
for(; q->server < n; q->server++){
|
||||
if(param->remsock != INVALID_SOCKET){
|
||||
param->srv->so._closesocket(param->sostate, param->remsock);
|
||||
param->remsock = INVALID_SOCKET;
|
||||
}
|
||||
/* another socket for the loop to watch */
|
||||
ss->regsock = INVALID_SOCKET;
|
||||
q->tcp = c->nservers[q->server].usetcp;
|
||||
q->tcp = ns[q->server].usetcp;
|
||||
q->got = 0;
|
||||
{
|
||||
unsigned short id = (unsigned short)myrand();
|
||||
@ -166,7 +167,7 @@ static int dnsforward(struct udploop *u, struct udpsession *ss){
|
||||
q->query[2] = (unsigned char)(id >> 8);
|
||||
q->query[3] = (unsigned char)(id & 0xff);
|
||||
}
|
||||
if((param->remsock = param->srv->so._socket(param->sostate, SASOCK(&c->nservers[q->server].addr),
|
||||
if((param->remsock = param->srv->so._socket(param->sostate, SASOCK(&ns[q->server].addr),
|
||||
q->tcp? SOCK_STREAM : SOCK_DGRAM, q->tcp? IPPROTO_TCP : IPPROTO_UDP)) == INVALID_SOCKET) continue;
|
||||
#ifdef _WIN32
|
||||
ioctlsocket(param->remsock, FIONBIO, &ul);
|
||||
@ -174,9 +175,9 @@ static int dnsforward(struct udploop *u, struct udpsession *ss){
|
||||
fcntl(param->remsock, F_SETFL, O_NONBLOCK | fcntl(param->remsock, F_GETFL));
|
||||
#endif
|
||||
memset(¶m->sinsl, 0, sizeof(param->sinsl));
|
||||
*SAFAMILY(¶m->sinsl) = *SAFAMILY(&c->nservers[q->server].addr);
|
||||
*SAFAMILY(¶m->sinsl) = *SAFAMILY(&ns[q->server].addr);
|
||||
if(bindwithrange(param, param->remsock, ¶m->sinsl, param->extport)) continue;
|
||||
param->sinsr = c->nservers[q->server].addr;
|
||||
param->sinsr = ns[q->server].addr;
|
||||
ss->idle = 0;
|
||||
ss->deadline = time(NULL) + param->srv->timeouts[DNS_TO];
|
||||
if(q->tcp){
|
||||
@ -261,7 +262,7 @@ static int dnsprfirst(struct udploop *u, struct udpsession *ss, unsigned char *q
|
||||
/* the name cache only: what it does not have the name servers are
|
||||
asked, without waiting here */
|
||||
else if((q->type==0x01 || q->type==0x1c) && !param->srv->s_option){
|
||||
ip = hashresolv(q->type==0x1c? &dns6_table : &dns_table, (unsigned char *)q->host, q->addr, &q->ttl);
|
||||
ip = nscacheresolv(q->type==0x1c? AF_INET6 : AF_INET, (unsigned char *)q->host, q->addr, &q->ttl);
|
||||
}
|
||||
len += 5;
|
||||
|
||||
@ -295,7 +296,7 @@ static int dnsprfirst(struct udploop *u, struct udpsession *ss, unsigned char *q
|
||||
}
|
||||
}
|
||||
q->ip = ip;
|
||||
if(ip || !param->srv->config->numservers){
|
||||
if(ip || !numservers){
|
||||
param->res = dnsanswer(u, param, q, buf, len);
|
||||
free(buf);
|
||||
return 1;
|
||||
|
||||
@ -19,6 +19,19 @@ static void char_index2hash(const struct hashtable *ht, void *index, uint8_t *ha
|
||||
}
|
||||
}
|
||||
|
||||
/* A host name, whose case does not matter: hashed as lower case */
|
||||
static void dns_index2hash(const struct hashtable *ht, void *index, uint8_t *hash){
|
||||
char low[1024];
|
||||
size_t i, len = strlen((const char *)index);
|
||||
|
||||
if(len >= sizeof(low)) {
|
||||
char_index2hash(ht, index, hash);
|
||||
return;
|
||||
}
|
||||
for(i = 0; i <= len; i++) low[i] = (char)tolower(((unsigned char *)index)[i]);
|
||||
char_index2hash(ht, low, hash);
|
||||
}
|
||||
|
||||
static void param2hash_add(const struct hashtable *ht, void *index, uint8_t *hash){
|
||||
struct clientparam *param = (struct clientparam *)index;
|
||||
|
||||
@ -76,8 +89,11 @@ void param2hash_search(const struct hashtable *ht, void *index, uint8_t *hash){
|
||||
memcpy(param->hash, hash, ht->hash_size);
|
||||
}
|
||||
|
||||
struct hashtable dns_table = {char_index2hash, char_index2hash, 4, 32};
|
||||
struct hashtable dns6_table = {char_index2hash, char_index2hash, 16, 32};
|
||||
struct hashtable dns_table = {dns_index2hash, dns_index2hash, 4, 32};
|
||||
struct hashtable dns6_table = {dns_index2hash, dns_index2hash, 16, 32};
|
||||
/* the hosts file, while the system's resolver is the default */
|
||||
struct hashtable hosts_table = {dns_index2hash, dns_index2hash, 4, 32};
|
||||
struct hashtable hosts6_table = {dns_index2hash, dns_index2hash, 16, 32};
|
||||
struct hashtable auth_table = {param2hash_add, param2hash_search, sizeof(struct authcache), 64};
|
||||
/* users: each configuration has a table of its own, made from this one */
|
||||
const struct hashtable pwl_template = {char_index2hash, char_index2hash, 64, 64};
|
||||
|
||||
@ -45,7 +45,7 @@ struct symbol symbols[] = {
|
||||
{symbols+18, "ipauth", (void *) ipauth},
|
||||
{symbols+19, "strongauth", (void *) strongauth},
|
||||
{symbols+20, "checkACL", (void *) checkACL},
|
||||
{symbols+21, "nservers", (void *) confboot.nservers},
|
||||
{symbols+21, "nservers", (void *) nservers},
|
||||
{symbols+22, "udpresolve", (void *) udpresolve},
|
||||
{symbols+23, "bandlim_mutex", (void *) &bandlim_mutex},
|
||||
{symbols+24, "tc_mutex", (void *) &tc_mutex},
|
||||
@ -87,7 +87,7 @@ static void * findbyname(const char *name){
|
||||
struct pluginlink pluginlink = {
|
||||
{symbols, "", NULL},
|
||||
&confboot,
|
||||
confboot.nservers,
|
||||
nservers,
|
||||
&linenum,
|
||||
authfuncs,
|
||||
commandhandlers,
|
||||
|
||||
10
src/proxy.h
10
src/proxy.h
@ -428,6 +428,16 @@ int parseconnusername(char *username, struct clientparam *param, int extpasswd,
|
||||
int ACLmatches(struct ace* acentry, struct clientparam * param);
|
||||
int checkACL(struct clientparam * param);
|
||||
char * aceaction (int action);
|
||||
extern struct nserver nservers[MAXNSERVERS];
|
||||
extern int numservers;
|
||||
int getnservers(struct nserver *ns);
|
||||
int nscacheresolv(int af, unsigned char *name, unsigned char *value, uint32_t *ttl);
|
||||
void setnservers(const struct nserver *ns, int n);
|
||||
void sysdnsinit(void);
|
||||
int sysdnscount(void);
|
||||
void sysdnsapply(struct extparam *c, const struct nserver *ns, int n);
|
||||
void sysdnscheck(void);
|
||||
void confdnscommit(struct extparam *c);
|
||||
uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_t *retttl, struct clientparam* param, int makeauth);
|
||||
|
||||
struct ace * copyacl (struct ace *ac);
|
||||
|
||||
397
src/resolve.c
397
src/resolve.c
@ -1,5 +1,30 @@
|
||||
#include "proxy.h"
|
||||
|
||||
/* The name servers in force, for the process and not a configuration:
|
||||
those of nserver lines, or else the system's. Read as a copy, they may
|
||||
change under a resolution. */
|
||||
struct nserver nservers[MAXNSERVERS];
|
||||
int numservers = 0;
|
||||
static _3proxy_mutex_t nservers_mutex;
|
||||
|
||||
int getnservers(struct nserver *ns){
|
||||
int n;
|
||||
|
||||
_3proxy_mutex_lock(&nservers_mutex);
|
||||
n = numservers;
|
||||
if(ns) memcpy(ns, nservers, n * sizeof(*ns));
|
||||
_3proxy_mutex_unlock(&nservers_mutex);
|
||||
return n;
|
||||
}
|
||||
|
||||
void setnservers(const struct nserver *ns, int n){
|
||||
if(n > MAXNSERVERS) n = MAXNSERVERS;
|
||||
_3proxy_mutex_lock(&nservers_mutex);
|
||||
if(n) memcpy(nservers, ns, n * sizeof(*ns));
|
||||
numservers = n;
|
||||
_3proxy_mutex_unlock(&nservers_mutex);
|
||||
}
|
||||
|
||||
|
||||
static int dnsqmatch(const unsigned char *a, const unsigned char *b, int len){
|
||||
int i;
|
||||
@ -19,14 +44,12 @@ static uint32_t udpresolvec(struct extparam *c, int af, unsigned char * name, un
|
||||
|
||||
int i,n;
|
||||
uint32_t retval;
|
||||
struct nserver ns[MAXNSERVERS];
|
||||
|
||||
if((af == AF_INET) && (retval = hashresolv(&dns_table, name, value, retttl))) {
|
||||
if((af == AF_INET || af == AF_INET6) && (retval = nscacheresolv(af, name, value, retttl))) {
|
||||
return retval;
|
||||
}
|
||||
if((af == AF_INET6) && (retval = hashresolv(&dns6_table, name, value, retttl))) {
|
||||
return retval;
|
||||
}
|
||||
n = (makeauth && !SAISNULL(&c->authnserver.addr))? 1 : c->numservers;
|
||||
n = (makeauth && !SAISNULL(&c->authnserver.addr))? 1 : getnservers(ns);
|
||||
for(i=0; i<n; i++){
|
||||
unsigned short nq, na;
|
||||
unsigned char b[4098], *buf, *s1, *s2;
|
||||
@ -54,8 +77,8 @@ static uint32_t udpresolvec(struct extparam *c, int af, unsigned char * name, un
|
||||
*SAFAMILY(sinsl) = *SAFAMILY(&c->authnserver.addr);
|
||||
}
|
||||
else {
|
||||
usetcp = c->nservers[i].usetcp;
|
||||
*SAFAMILY(sinsl) = *SAFAMILY(&c->nservers[i].addr);
|
||||
usetcp = ns[i].usetcp;
|
||||
*SAFAMILY(sinsl) = *SAFAMILY(&ns[i].addr);
|
||||
}
|
||||
if((sock=so._socket(so.state, SASOCK(sinsl), usetcp?SOCK_STREAM:SOCK_DGRAM, usetcp?IPPROTO_TCP:IPPROTO_UDP)) == INVALID_SOCKET) break;
|
||||
if(so._bind(so.state, sock,(struct sockaddr *)sinsl,SASIZE(sinsl))){
|
||||
@ -67,7 +90,7 @@ static uint32_t udpresolvec(struct extparam *c, int af, unsigned char * name, un
|
||||
*sinsr = c->authnserver.addr;
|
||||
}
|
||||
else {
|
||||
*sinsr = c->nservers[i].addr;
|
||||
*sinsr = ns[i].addr;
|
||||
}
|
||||
if(usetcp){
|
||||
if(connectwithpoll(NULL, sock,(struct sockaddr *)sinsr,SASIZE(sinsr),c->timeouts[CONNECT_TO])) {
|
||||
@ -245,4 +268,360 @@ uint32_t fakeresolver (int af, unsigned char *name, unsigned char * value){
|
||||
value[3] = 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* The system's name servers and hosts file, the default for a configuration
|
||||
without nserver (or fakeresolve): read at start, before a chroot can hide
|
||||
them, and checked from the main loop for changes, which apply while the
|
||||
default is in force. Hosts entries have tables of their own, emptied when
|
||||
the default is not in force. Only the main thread works on this. */
|
||||
#define HOSTSEXPIRES ((sizeof(time_t) > 4)? (time_t)0xfffffffe : (time_t)0x7ffffffe)
|
||||
/* the time to live a hosts file name is given in an answer */
|
||||
#define HOSTSTTL 60
|
||||
#define SYSDNSCHECK 5
|
||||
|
||||
static struct {
|
||||
struct nserver ns[MAXNSERVERS];
|
||||
int n; /* as last read */
|
||||
int active; /* the default in force */
|
||||
char *hosts; /* the hosts file as last read */
|
||||
struct stat hsb; /* and how it was then */
|
||||
int hok;
|
||||
char *names; /* hosts names imported: family, name, 0 */
|
||||
size_t namesl, namess;
|
||||
time_t checked;
|
||||
} sysdns;
|
||||
|
||||
/* An address of a name server, put after the others unless one of them */
|
||||
static int addns(struct nserver *ns, int n, const char *s){
|
||||
PROXYSOCKADDRTYPE sa;
|
||||
int i;
|
||||
|
||||
if(n >= MAXNSERVERS) return n;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
if(inet_pton(AF_INET, s, SAADDR(&sa)) > 0) *SAFAMILY(&sa) = AF_INET;
|
||||
#ifndef NOIPV6
|
||||
else if(inet_pton(AF_INET6, s, SAADDR(&sa)) > 0) *SAFAMILY(&sa) = AF_INET6;
|
||||
#endif
|
||||
else return n; /* a link-local one with %scope is not */
|
||||
*SAPORT(&sa) = htons(53);
|
||||
for(i = 0; i < n; i++)
|
||||
if(!memcmp(&ns[i].addr, &sa, sizeof(sa))) return n;
|
||||
memset(ns + n, 0, sizeof(*ns));
|
||||
ns[n].addr = sa;
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
/* Addresses in a list, separated by spaces or commas */
|
||||
static int addnslist(struct nserver *ns, int n, const char *s){
|
||||
char a[64];
|
||||
int l;
|
||||
|
||||
while(*s && n < MAXNSERVERS){
|
||||
while(*s == ' ' || *s == ',' || *s == '\t') s++;
|
||||
for(l = 0; *s && *s != ' ' && *s != ',' && *s != '\t'; s++) if(l < 63) a[l++] = *s;
|
||||
a[l] = 0;
|
||||
if(l) n = addns(ns, n, a);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
/* A string value, a multi-string one as one list; 0 if there is none */
|
||||
static int regstr(HKEY k, const char *name, char *buf, DWORD size){
|
||||
DWORD type, len = size - 2, i;
|
||||
|
||||
if(RegQueryValueExA(k, name, NULL, &type, (LPBYTE)buf, &len) || (type != REG_SZ && type != REG_MULTI_SZ)) return 0;
|
||||
buf[len] = buf[len + 1] = 0;
|
||||
if(type == REG_MULTI_SZ) for(i = 0; i + 1 < len; i++) if(!buf[i]) buf[i] = ' ';
|
||||
return *buf != 0;
|
||||
}
|
||||
|
||||
/* Whether an interface has an address of its own, as it does unless
|
||||
disconnected: what DHCP gave it or the first one set */
|
||||
static int regup(HKEY k){
|
||||
char buf[256];
|
||||
|
||||
if(regstr(k, "DhcpIPAddress", buf, sizeof(buf)) && strncmp(buf, "0.0.0.0", 7)) return 1;
|
||||
return regstr(k, "IPAddress", buf, sizeof(buf)) && strncmp(buf, "0.0.0.0", 7);
|
||||
}
|
||||
|
||||
/* The name servers of a key's interfaces: those set, or with dhcp those
|
||||
DHCP gave; for IPv4 only of an interface with an address */
|
||||
static int regifaces(struct nserver *ns, int n, const char *path, const char *value, int needup){
|
||||
HKEY k, ik;
|
||||
char name[256], buf[1024];
|
||||
DWORD i, len;
|
||||
|
||||
if(RegOpenKeyExA(HKEY_LOCAL_MACHINE, path, 0, KEY_READ, &k)) return n;
|
||||
for(i = 0; n < MAXNSERVERS; i++){
|
||||
len = sizeof(name);
|
||||
if(RegEnumKeyExA(k, i, name, &len, NULL, NULL, NULL, NULL)) break;
|
||||
if(RegOpenKeyExA(k, name, 0, KEY_READ, &ik)) continue;
|
||||
if((!needup || regup(ik)) && regstr(ik, value, buf, sizeof(buf))) n = addnslist(ns, n, buf);
|
||||
RegCloseKey(ik);
|
||||
}
|
||||
RegCloseKey(k);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* As Windows has them: set for the system, set for an interface, then
|
||||
those DHCP gave */
|
||||
static int readsysns(struct nserver *ns){
|
||||
HKEY k;
|
||||
char buf[1024];
|
||||
int n = 0;
|
||||
static const char tcpip[] = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters";
|
||||
|
||||
if(!RegOpenKeyExA(HKEY_LOCAL_MACHINE, tcpip, 0, KEY_READ, &k)){
|
||||
if(regstr(k, "NameServer", buf, sizeof(buf))) n = addnslist(ns, n, buf);
|
||||
RegCloseKey(k);
|
||||
}
|
||||
n = regifaces(ns, n, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces", "NameServer", 1);
|
||||
#ifndef NOIPV6
|
||||
n = regifaces(ns, n, "SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters\\Interfaces", "NameServer", 0);
|
||||
#endif
|
||||
n = regifaces(ns, n, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces", "DhcpNameServer", 1);
|
||||
if(n < MAXNSERVERS && !RegOpenKeyExA(HKEY_LOCAL_MACHINE, tcpip, 0, KEY_READ, &k)){
|
||||
if(regstr(k, "DhcpNameServer", buf, sizeof(buf))) n = addnslist(ns, n, buf);
|
||||
RegCloseKey(k);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static const char *hostspath(void){
|
||||
static char path[MAX_PATH + 32];
|
||||
UINT l;
|
||||
|
||||
if(!*path){
|
||||
l = GetSystemDirectoryA(path, MAX_PATH);
|
||||
if(!l || l >= MAX_PATH) strcpy(path, "C:\\Windows\\System32");
|
||||
strcat(path, "\\drivers\\etc\\hosts");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
#else
|
||||
/* As resolv.conf has them, in its order */
|
||||
static int readsysns(struct nserver *ns){
|
||||
FILE *f;
|
||||
char line[512], *s, *e;
|
||||
int n = 0;
|
||||
|
||||
if(!(f = fopen("/etc/resolv.conf", "r"))) return 0;
|
||||
while(n < MAXNSERVERS && fgets(line, sizeof(line), f)){
|
||||
if(strncmp(line, "nameserver", 10) || (line[10] != ' ' && line[10] != '\t')) continue;
|
||||
for(s = line + 10; *s == ' ' || *s == '\t'; s++);
|
||||
for(e = s; *e && !isspace((unsigned char)*e) && *e != '#' && *e != ';'; e++);
|
||||
*e = 0;
|
||||
n = addns(ns, n, s);
|
||||
}
|
||||
fclose(f);
|
||||
return n;
|
||||
}
|
||||
|
||||
static const char *hostspath(void){
|
||||
return "/etc/hosts";
|
||||
}
|
||||
#endif
|
||||
|
||||
static char *readhosts(struct stat *sb){
|
||||
FILE *f;
|
||||
char *buf;
|
||||
long l;
|
||||
|
||||
if(stat(hostspath(), sb) || !(f = fopen(hostspath(), "rb"))) return NULL;
|
||||
fseek(f, 0, SEEK_END);
|
||||
l = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if(l < 0 || l > 64*1024*1024 || !(buf = malloc((size_t)l + 1))){
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
l = (long)fread(buf, 1, (size_t)l, f);
|
||||
buf[l] = 0;
|
||||
fclose(f);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* The hosts names imported before taken out */
|
||||
static void hostsremove(void){
|
||||
size_t i;
|
||||
|
||||
for(i = 0; i < sysdns.namesl; i += strlen(sysdns.names + i + 1) + 2)
|
||||
hashdelete(sysdns.names[i] == 6? &hosts6_table : &hosts_table, sysdns.names + i + 1);
|
||||
sysdns.namesl = 0;
|
||||
}
|
||||
|
||||
static void hostsname(int af, const char *name){
|
||||
size_t l = strlen(name) + 2;
|
||||
char *p;
|
||||
|
||||
if(sysdns.namesl + l > sysdns.namess){
|
||||
size_t ns = (sysdns.namess + l) * 2;
|
||||
|
||||
if(!(p = realloc(sysdns.names, ns))) return;
|
||||
sysdns.names = p;
|
||||
sysdns.namess = ns;
|
||||
}
|
||||
sysdns.names[sysdns.namesl] = (char)af;
|
||||
memcpy(sysdns.names + sysdns.namesl + 1, name, l - 1);
|
||||
sysdns.namesl += l;
|
||||
}
|
||||
|
||||
/* The hosts file imported: its first address of a family for a name, as
|
||||
the C library takes it */
|
||||
static void hostsimport(void){
|
||||
char *s, *e, *next, *tok, addr[16], name[256];
|
||||
struct hashtable *ht;
|
||||
unsigned char old[16];
|
||||
uint32_t ttl;
|
||||
int af, l;
|
||||
|
||||
hostsremove();
|
||||
if(!sysdns.hosts) return;
|
||||
for(s = sysdns.hosts; *s; s = next){
|
||||
for(e = s; *e && *e != '\n' && *e != '\r' && *e != '#'; e++);
|
||||
for(next = e; *next && *next != '\n'; next++);
|
||||
if(*next) next++;
|
||||
/* the address */
|
||||
while(s < e && (*s == ' ' || *s == '\t')) s++;
|
||||
for(tok = s; s < e && *s != ' ' && *s != '\t'; s++);
|
||||
if((l = (int)(s - tok)) <= 0 || l >= (int)sizeof(name)) continue;
|
||||
memcpy(name, tok, l);
|
||||
name[l] = 0;
|
||||
if(inet_pton(AF_INET, name, addr) > 0) af = 4;
|
||||
#ifndef NOIPV6
|
||||
else if(inet_pton(AF_INET6, name, addr) > 0) af = 6;
|
||||
#endif
|
||||
else continue;
|
||||
ht = (af == 6)? &hosts6_table : &hosts_table;
|
||||
/* its names */
|
||||
for(;;){
|
||||
while(s < e && (*s == ' ' || *s == '\t')) s++;
|
||||
if(s >= e) break;
|
||||
for(tok = s; s < e && *s != ' ' && *s != '\t'; s++);
|
||||
if((l = (int)(s - tok)) >= (int)sizeof(name)) continue;
|
||||
for(l = 0; tok + l < s; l++) name[l] = (char)tolower((unsigned char)tok[l]);
|
||||
name[l] = 0;
|
||||
/* one before in the file */
|
||||
if(hashresolv(ht, name, old, &ttl)) continue;
|
||||
hashadd(ht, name, addr, HOSTSEXPIRES);
|
||||
hostsname(af, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The system's name servers and hosts file read again; what cannot be read
|
||||
now (after a chroot) or has no name server stays as it was. Returns 1 for
|
||||
name servers changed, 2 for the hosts file, as a mask. */
|
||||
static int sysdnsread(void){
|
||||
struct nserver ns[MAXNSERVERS];
|
||||
struct stat sb;
|
||||
char *h;
|
||||
int n, res = 0;
|
||||
|
||||
memset(ns, 0, sizeof(ns));
|
||||
n = readsysns(ns);
|
||||
if(n && (n != sysdns.n || memcmp(ns, sysdns.ns, n * sizeof(*ns)))){
|
||||
memcpy(sysdns.ns, ns, sizeof(ns));
|
||||
sysdns.n = n;
|
||||
res |= 1;
|
||||
}
|
||||
if(!stat(hostspath(), &sb) && (!sysdns.hok || sb.st_mtime != sysdns.hsb.st_mtime || sb.st_size != sysdns.hsb.st_size
|
||||
#ifndef _WIN32
|
||||
|| sb.st_ino != sysdns.hsb.st_ino || sb.st_ctime != sysdns.hsb.st_ctime
|
||||
#endif
|
||||
) && (h = readhosts(&sb))){
|
||||
free(sysdns.hosts);
|
||||
sysdns.hosts = h;
|
||||
sysdns.hsb = sb;
|
||||
sysdns.hok = 1;
|
||||
res |= 2;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Read at start, before the configuration and a chroot in it, and in force
|
||||
while it is read: its host names are resolved with them, nserver lines
|
||||
taking their place once it is committed */
|
||||
void sysdnsinit(void){
|
||||
_3proxy_mutex_init(&nservers_mutex);
|
||||
inithashtable(&hosts_table, 256, 256, 1 << 22);
|
||||
#ifndef NOIPV6
|
||||
inithashtable(&hosts6_table, 256, 256, 1 << 22);
|
||||
#endif
|
||||
sysdnsread();
|
||||
sysdns.checked = time(NULL);
|
||||
if(!sysdns.n) return;
|
||||
sysdns.active = 1;
|
||||
setnservers(sysdns.ns, sysdns.n);
|
||||
resolvfunc = myresolver;
|
||||
hostsimport();
|
||||
}
|
||||
|
||||
/* A name known without asking: one nsrecord made first, then the hosts
|
||||
file while it is in force, then what name servers answered */
|
||||
int nscacheresolv(int af, unsigned char *name, unsigned char *value, uint32_t *ttl){
|
||||
struct hashtable *cache = (af == AF_INET6)? &dns6_table : &dns_table;
|
||||
uint32_t t = 0;
|
||||
int r;
|
||||
|
||||
r = hashresolv(cache, name, value, &t);
|
||||
if(r && t > 3600*24*365){
|
||||
if(ttl) *ttl = t;
|
||||
return r;
|
||||
}
|
||||
if(sysdns.active && hashresolv((af == AF_INET6)? &hosts6_table : &hosts_table, name, value, NULL)){
|
||||
if(ttl) *ttl = HOSTSTTL;
|
||||
return 1;
|
||||
}
|
||||
if(r && ttl) *ttl = t;
|
||||
return r;
|
||||
}
|
||||
|
||||
int sysdnscount(void){
|
||||
return sysdns.n;
|
||||
}
|
||||
|
||||
/* A configuration committed: the name servers of its nserver lines, else,
|
||||
with nothing else to resolve names, the system's and its hosts file, read
|
||||
again. Until then those in force stay. */
|
||||
void sysdnsapply(struct extparam *c, const struct nserver *ns, int n){
|
||||
sysdns.active = 0;
|
||||
if(n){
|
||||
setnservers(ns, n);
|
||||
hostsremove();
|
||||
return;
|
||||
}
|
||||
if(c->resolvfunc){
|
||||
setnservers(NULL, 0);
|
||||
hostsremove();
|
||||
return;
|
||||
}
|
||||
sysdnsread();
|
||||
sysdns.checked = curtime;
|
||||
sysdns.active = 1;
|
||||
setnservers(sysdns.ns, sysdns.n);
|
||||
if(!sysdns.n) return; /* the C library, as before */
|
||||
c->resolvfunc = myresolver;
|
||||
hostsimport();
|
||||
}
|
||||
|
||||
/* From the main loop: name servers and hosts file read again while the
|
||||
default is in force, every few seconds. What cannot be read (after a
|
||||
chroot) is left as it was. */
|
||||
void sysdnscheck(void){
|
||||
int was = sysdns.n, res;
|
||||
|
||||
if(!sysdns.active || curtime - sysdns.checked < SYSDNSCHECK) return;
|
||||
sysdns.checked = curtime;
|
||||
res = sysdnsread();
|
||||
if(res & 1){
|
||||
setnservers(sysdns.ns, sysdns.n);
|
||||
dolog(NULL, (unsigned char *)"System name servers changed");
|
||||
/* none before: the C library resolved until now */
|
||||
if(!was) conf->resolvfunc = resolvfunc = myresolver;
|
||||
}
|
||||
if(resolvfunc == myresolver && ((res & 2) || !was)) hostsimport();
|
||||
if(res & 2) dolog(NULL, (unsigned char *)"Hosts file changed");
|
||||
}
|
||||
|
||||
@ -502,7 +502,7 @@ struct nserver {
|
||||
int usetcp;
|
||||
};
|
||||
|
||||
#define MAXNSERVERS 5
|
||||
#define MAXNSERVERS 3
|
||||
#define MAXRADIUS 5
|
||||
|
||||
struct radserver {
|
||||
@ -919,8 +919,7 @@ struct extparam {
|
||||
time_t logtime;
|
||||
unsigned logdumpsrv, logdumpcli;
|
||||
char delimchar;
|
||||
struct nserver nservers[MAXNSERVERS], authnserver;
|
||||
int numservers;
|
||||
struct nserver authnserver;
|
||||
struct radserver radiuslist[MAXRADIUS];
|
||||
int nradservers;
|
||||
char radiussecret[64];
|
||||
@ -999,6 +998,8 @@ struct child {
|
||||
|
||||
extern struct hashtable dns_table;
|
||||
extern struct hashtable dns6_table;
|
||||
extern struct hashtable hosts_table;
|
||||
extern struct hashtable hosts6_table;
|
||||
extern struct hashtable auth_table;
|
||||
extern const struct hashtable pwl_template;
|
||||
|
||||
|
||||
83
tests/cases/sysdns.py
Normal file
83
tests/cases/sysdns.py
Normal file
@ -0,0 +1,83 @@
|
||||
"""The system's name servers and hosts file as the default resolver: with
|
||||
no nserver in a configuration, names in the hosts file are answered from
|
||||
it, through the name cache; with nserver lines, the file is not used."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
def hosts_path():
|
||||
if sys.platform == "win32":
|
||||
return os.path.join(os.environ.get("SystemRoot", r"C:\Windows"),
|
||||
"System32", "drivers", "etc", "hosts")
|
||||
return "/etc/hosts"
|
||||
|
||||
|
||||
def hosts_localhost():
|
||||
"""Whether the hosts file maps localhost to 127.0.0.1 itself."""
|
||||
try:
|
||||
with open(hosts_path(), errors="replace") as f:
|
||||
for line in f:
|
||||
words = line.split("#", 1)[0].split()
|
||||
if len(words) > 1 and words[0] == "127.0.0.1" and "localhost" in [w.lower() for w in words[1:]]:
|
||||
return True
|
||||
except OSError:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def system_nameserver():
|
||||
"""Whether there is a name server for 3proxy to find: resolv.conf has
|
||||
one, or on Windows the registry is taken to."""
|
||||
if sys.platform == "win32":
|
||||
return True
|
||||
try:
|
||||
with open("/etc/resolv.conf") as f:
|
||||
return any(re.match(r"nameserver\s+[0-9a-fA-F.:]+\s*$", l.split("#")[0]) for l in f)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def wait(t, port, name):
|
||||
deadline = time.time() + 5
|
||||
while time.time() < deadline:
|
||||
if t.dns_query(port, name):
|
||||
return
|
||||
|
||||
|
||||
def run(t):
|
||||
if not hosts_localhost() or not system_nameserver():
|
||||
t.skip("no localhost in the hosts file, or no system name server")
|
||||
return
|
||||
|
||||
port = t.free_port()
|
||||
t.start("sysdns", f"""
|
||||
log
|
||||
flush
|
||||
auth iponly
|
||||
allow *
|
||||
dnspr -p{port}
|
||||
""")
|
||||
wait(t, port, "localhost")
|
||||
t.eq(["127.0.0.1"], t.dns_query(port, "localhost"),
|
||||
"without nserver, a name of the hosts file is answered from it")
|
||||
t.eq(["127.0.0.1"], t.dns_query(port, "LocalHost"),
|
||||
"whatever its case")
|
||||
|
||||
# with a name server configured, one that answers nothing, the hosts
|
||||
# file is not used
|
||||
other = t.free_port()
|
||||
t.start("sysdns_nserver", f"""
|
||||
log
|
||||
flush
|
||||
nserver 127.0.0.1:{t.free_port()}
|
||||
nscache 1024
|
||||
auth iponly
|
||||
allow *
|
||||
dnspr -p{other}
|
||||
""")
|
||||
wait(t, other, "localhost")
|
||||
t.ne(["127.0.0.1"], t.dns_query(other, "localhost") or [],
|
||||
"with nserver, the hosts file is not used")
|
||||
Loading…
Reference in New Issue
Block a user