1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
|
#!/bin/bash
# Copyright 2006 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# /etc/pdnsd/pdnsd.conf updater
# Written by Oldrich Jedlicka (oldium.pro@seznam.cz)
# Implementation notes:
# * The assumptions are same as for other scripts like bind and dnsmasq:
# - We assume that we are a local dns cache - after all, why would a server
# use resolvconf?
# - Now that we have assumed this, we also assume that generic DHCP clients
# will enter their domains and search domains ONLY in the "search" field
# in their resolv.confs and VPN clients will put the domain they are for
# into the domain field only.
# - This allows pdnsd to forward domains for a specific VPN domain to the
# VPN nameserver and everything else to the standard name servers.
#
# HOW-TO CONFIGURE:
#
# To get this working, you need to do only two steps
#
# 1. Create a basic configuration of /etc/pdnsd/pdnsd.conf, you can use
# /etc/pdnsd/pdnsd.conf.example to start.
#
# Additional configuration will be created automatically be resolvconf.
# The generated server sections has labels starting with "resolvconf", so
#
# DO NOT USE "resolvconf" IN YOUR LABELS!
#
# Check if the status_ctl is set to "on", otherwise the configuration
# will not be automatically reloaded - see sample config file.
#
# You are free to edit automatically created server sections, but always
# write one option per line. There are few options that are always recreated
# and your changes in them will be lost. Here is the list (with example
# values):
#
# preset=on;
# ip="192.168.0.1","192.168.0.2";
# include=".net",".com";'
#
# The exclude directive in "resolvconf" server section is partly recreated.
# Known (configured) domains in the form ".<domain>" or ".<domain>." are
# added and removed automatically, unknown domains (also those not in
# the format above) found in this directive are kept.
#
# The sample configuration file /etc/pdnsd/pdnsd.conf prepared to work
# with resolvconf would look like this:
#
#global {
# perm_cache=2048;
# run_as="pdnsd";
# status_ctl = on; # Important to enable status control
# run_ipv4=on;
# par_queries=2; # How many servers are probed in parallel
# interface = "lo"; # Interface on which the pdnsd listens
#}
#
# 2. The last step is to configure dns configuration for /etc/resolv.conf
# for the lo interface. In Gentoo we set it up like so in /etc/conf.d/net
#
# dns_servers_lo=( "127.0.0.1" )
# pdnsd config file
PDNSDCONFIG="/etc/pdnsd/pdnsd.conf"
# Backup suffix
BACKUPSUFFIX=".backup"
# Load our variables from resolvconf
VARS="$(resolvconf -v)"
eval "${VARS}"
COMMENT='
# Automatically generated by resolvconf.
#
# Following server sections are automatically enabled and disabled.
#
# !!! WARNING !!!
# DO NOT RENAME LABELS!
#
# No section will be deleted and only some options are automatically changed.
# Feel free to add your own options, but do not use pair comments /* */ as they
# are not recognised.
#
# DO NOT USE resolvconf ANYWHERE IN YOUR LABELS!
#
# Automatically changed options are (with examples):
# preset=on;
# ip="192.168.0.1","192.168.0.2";
# include=".net",".com";
# exclude=".domain.net",".domain.com";
# policy=excluded;
#
# The exclude directive is changed automatically only in "resolvconf" server
# section. Not handled servers are kept in the directive.
#'
BASIC_SETTINGS='server {
label="resolvconf";
preset=off;
}'
INSTALLATION_CHECK='^[[:space:]]*label[[:space:]]*=[[:space:]]*"resolvconf"'
###
# Sed script configuration
#
# Composed sequence of lines:
#
# (1) SED_LOOP with @MATCH_LABELS@ substituted by several SED_MATCH_ONE_LABEL
# (2) SED_EDIT_ONE_SERVER several times
# (3) SED_ADDING with new servers
#
# Notes:
#
# * @LABEL@ is a string "resolvconf-<domain>" or "resolvconf" for global
# section
# * @RULE@ is @LABEL@ with translated characters '-' and '.' into '_'.
###
###
# Main loop with label match - it will redirect the processing to
# SED_EDIT_ONE_SERVER, when the label match is found. Special match is
# for "resolvconf" label - the control flow is redirected to SED_ADDING to
# allow adding new sections.
#
# To summarize: Old sections are edited as they appear in the file and new
# sections are added before the "resolvconf" section.
SED_LOOP=\
'/^[[:space:]]*server[[:space:]]*[\{]/ b server;
p; d;
:server; h;
:server_loop; n;
/^[[:space:]]*server[[:space:]]*[\{]/ { x; p; b server_loop; };
@MATCH_LABELS@
/^[[:space:]]*label[[:space:]]*=[[:space:]]*"resolvconf"/ { H; b adding; };
/^[[:space:]]*[\}]/ { H; x; p; d; };
H;
b server_loop;
'
###
# Match for one label with a jump to SED_EDIT_ONE_SERVER
SED_MATCH_ONE_LABEL=\
'/^[[:space:]]*label[[:space:]]*=[[:space:]]*"@LABEL@"/ { H; x; b main_@RULE@; };
'
###
# Editing one server. New lines are put into @SETUP@, lines are composed
# in function compose_lines(). After the new lines are added, all "preset",
# "ip" and "include" options are removed (not printed).
#
# Sanity checks: Check if there is a second label or another server directive.
# In both cases, there is some error in the file, so go to the beginning by
# jumping to SED_LOOP's :server.
SED_EDIT_ONE_SERVER=\
':main_@RULE@;
p; @SETUP@
:loop_@RULE@;
n;
/^[[:space:]]*server[[:space:]]*[\{]/ b server;
/^[[:space:]]*label[[:space:]]*=/ b server;
/^[[:space:]]*preset[[:space:]]*=/ b loop_@RULE@;
/^[[:space:]]*ip[[:space:]]*=/ b loop_@RULE@;
/^[[:space:]]*include[[:space:]]*=/ b loop_@RULE@;
/^[[:space:]]*policy[[:space:]]*=/ b loop_@RULE@;
/^[[:space:]]*exclude[[:space:]]*=/ b exclude_logic_@RULE;
p;
/^[[:space:]]*[\}]/ d;
b loop_@RULE@;
:exclude_logic_@RULE;
@EXCLUDE_LOGIC@
b loop_@RULE@;
'
###
# Add new servers. All lines composed by function compose_lines() are put into
# @SETUP@. Then the control flow is returned to one special SED_EDIT_ONE_SERVER
# section with label "resolvconf".
SED_ADDING=\
':adding;
@SETUP@
x; b main_resolvconf;
'
###
# Edit the domain list (include/exclude). All empty fields and matching domains
# are removed. Unmaintained domains (not in resolvconf-<domain>) are kept. All
# domains should be in a pipe (|) separated list and should begin, but not end
# with a dot. The list is put into @DOMAINS@. The control flow continues, where
# it ended in SED_EDIT_ONE_SERVER.
#
SED_DOMAIN_LIST_LOGIC=\
'h;
s/^([[:space:]]*@DIRECTIVE@[[:space:]]*=[[:space:]]*).*/\\1/;
x;
s/^[[:space:]]*@DIRECTIVE@[[:space:]]*=[[:space:]]*//;
:@DIRECTIVE@_loop_@RULE@;
/([[:space:]]*("[^"]"*|[^,;]*)[[:space:]]*,)*[[:space:]]*("(@DOMAINS@|)\.?"|(@DOMAINS@)\.?|,)[[:space:]]*[,;]/ {
s/(([[:space:]]*("[^"]"*|[^,;]*)[[:space:]]*,)*[[:space:]]*)("(@DOMAINS@|)\.?"|(@DOMAINS@)\.?|,)[[:space:]]*([,;])/\\1\\7/;
b @DIRECTIVE@_loop_@RULE@;
};
s/^[,;]//g;
/^[[:space:]]*$/ b @DIRECTIVE@_end_@RULE@;
H; x; s/\\n//; p;
:@DIRECTIVE@_end_@RULE@;
'
################################################################################
# Functions
###
# char* [] uniqify(char* list[])
#
# Uniqify the items in the list
uniqify() {
local result=
while [ -n "$1" ] ; do
case " ${result} " in
*" $1 "*) ;;
*) result="${result} $1" ;;
esac
shift
done
echo "${result# *}"
}
###
# char *make_pdnsd_label(char *domain)
#
# Translate domain name into pdnsd's label
make_pdnsd_label() {
local domain=$1
if [[ -n ${domain} ]] ; then
echo -n "resolvconf-${domain}"
else
echo -n "resolvconf"
fi
}
###
# char *make_sed_label(char *pdnsd_label)
#
# Translate pdnsd's label into sed's label
make_sed_label() {
local label="$1"
label="${label//-/_}"
label="${label//./_}"
echo -n "${label}"
}
# char *compose_lines(...)
#
# Compose a sed command that prints lines
compose_lines() {
local line result
for line in "$@" ; do
result="${result}i\\\\\\n${line// /\\t}\\n"
done
echo "${result}"
}
###
# char *build_settings(char *nameservers, char *domains, char *directive)
#
# Builds configuration part @SETUP@ of sed script. The directive parameter denotes
# if the domains are to be included ("include") or excluded ("exclude"). This
# involves options like
#
# (1) # [nameserver list is empty]
# preset=off;
#
# (2) # [domain list is empty]
# preset=on;
# ip="address","address"...;
#
# (3) # [directive=="include"]
# preset=on;
# ip="address","address"...;
# include=".domain.",".domain."...;
# policy=excluded;
#
# (4) # [directive=="exclude"]
# preset=on;
# ip="address","address"...;
# exclude=".domain.",".domain."...;
# policy=included;
#
# Note: Currently there will always be only one domain in "include" directive.
#
build_settings() {
local ns="$1" domains="$2" directive="$3"
if [[ -n ${ns} ]] ; then
local x list_ns list_domains
for x in ${ns} ; do
list_ns="${list_ns},\"${x}\""
done
list_ns="${list_ns#,}"
if [[ -n ${domains} ]] ; then
for x in ${domains} ; do
list_domains="${list_domains},\".${x}.\""
done
list_domains="${list_domains#,}"
if [[ $directive == "include" ]]; then
compose_lines \
" preset=on;" \
" ip=${list_ns};" \
" include=${list_domains};" \
" policy=excluded;"
else
compose_lines \
" preset=on;" \
" ip=${list_ns};" \
" exclude=${list_domains};" \
" policy=included;"
fi
else
compose_lines \
" preset=on;" \
" ip=${list_ns};"
fi
else
compose_lines \
" preset=off;"
fi
}
###
# char *build_match_labels(char *domains...)
#
# Build the label match part of the sed script
#
build_match_labels() {
local domain result label destination new_match
for domain in "$@" ; do
label="$(make_pdnsd_label "${domain}")"
rule="$(make_sed_label "${label}")"
new_match="${SED_MATCH_ONE_LABEL//@LABEL@/${label}}"
new_match="${new_match//@RULE@/${rule}}"
result="${result}${new_match}"
done
echo "${result}"
}
###
# char *build_domain_list_logic(char *domains, char *directive)
#
# Build a logic for changing (removing) domains from a directive.
#
build_domain_list_logic() {
local domains="$1" directive="$2"
local x domain_list logic
# Domains should be pipe separated list
for x in ${domains}; do
x=".${x%.}"
x="${x//./\.}"
domain_list="${domain_list}|${x}"
done
domain_list="${domain_list#|}"
if [[ -z ${domain_list} ]]; then
logic="p;"
else
logic="${SED_DOMAIN_LIST_LOGIC//@DOMAINS@/${domain_list}}"
logic="${logic//@DIRECTIVE@/${directive}}"
fi
echo "${logic}"
}
###
# char *build_edit_part(char *domain, char *nameservers, \
# char *add_domains, char *remove_domains,
# char *directive)
#
# Build edit part of the sed script for a particular domain. Domain can be
# empty in the case it is the "resolvconf" server part.
#
build_edit_part() {
local domain="$1" nameservers="$2" add_domains="$3" remove_domains="$4"
local directive="$5"
local setup label rule logic result
setup="$(build_settings "${nameservers}" "${add_domains}" "${directive}")"
label="$(make_pdnsd_label "${domain}")"
rule="$(make_sed_label "${label}")"
logic="$(build_domain_list_logic "${remove_domains}" "${directive}")"
result="${SED_EDIT_ONE_SERVER//@SETUP@/${setup}}"
result="${result//@EXCLUDE_LOGIC@/${logic}}"
result="${result//@RULE@/${rule}}"
echo "${result}"
}
###
# char *get_domain_nameservers(char *domain, char *domain_config...)
#
# Get the list of nameservers belonging to one particular domain.
#
# Domain configuration is a space separated list of pair <domain>,<ip>.
#
get_domain_nameservers() {
local domain="$1" ns
shift
for x in "$@" ; do
if [[ ${x%,*} == ${domain} ]] ; then
ns="${ns} ${x#*,}"
fi
done
ns="$(uniqify ${ns})"
echo -n "${ns}"
}
###
# char *build_domain_edit_part(char *domain, char *domain_config...)
#
# Parse the list of domain configurations and build settings for one particular
# domain for the sed script.
#
# Domain configuration is a space separated list of pair <domain>,<ip>.
#
build_domain_edit_part() {
local domain="$1" ns
shift
ns="$(get_domain_nameservers "${domain}" "$@")"
build_edit_part "${domain}" "${ns}" "${domain}" "" "include"
}
###
# char *build_add_part(char *add, char *domains...)
#
# Build add part of the sed script for all domains that needs to be added
#
build_add_part() {
local add="$1" x label rule add_part new_part result
shift
for x in ${add} ; do
local domain="${x}" ns
ns="$(get_domain_nameservers "${domain}" "$@")"
label="$(make_pdnsd_label "${domain}")"
rule="$(make_sed_label ${label})"
new_part="$(compose_lines "server {" " label=\"${label}\";")"
new_part="${new_part}$(build_settings "${ns}" "${domain}" "include")"
new_part="${new_part}$(compose_lines "}" "")"
add_part="${add_part}${new_part}"
done
result="${SED_ADDING//@SETUP@/${add_part}}"
echo "${result}"
}
###
# char *build_sed_script(char *nameservers, char *domain_config,
# char *change, char *add,
# char *active_domains, char *known_domains)
#
# Build the full sed script from the list of nameservers, list of domains
# (in format <domain>,<ip>), list of changed domains, list of added domains,
# list of activly used domains and a list of all known domains.
#
build_sed_script() {
local ns="$1" domain_config="$2" change="$3" add="$4"
local active_domains="$5" known_domains="$6"
local match_labels="$(build_match_labels ${change})"
local edit_changed x
for x in ${change} ; do
edit_changed="${edit_changed}$( \
build_domain_edit_part "${x}" ${domain_config})"
done
edit_changed="${edit_changed}$( \
build_edit_part "" "${ns}" "${active_domains}" "${known_domains}" "exclude")"
local added
added="$(build_add_part "${add}" ${domain_config})"
local full
full="${SED_LOOP//@MATCH_LABELS@/${match_labels}}"
echo -ne "${full}"
echo -ne "${edit_changed}"
echo -ne "${added}"
}
###
# char *read_configured_domains(char *config_file)
#
# Reads labels of servers starting with resolvconf* from the configuration file.
#
read_configured_domains() {
local config_file="$1" result
result="\
$(sed -nre 's/^[[:space:]]+label=\"?resolvconf-([^;\"]*)\";.*/\1/p' \
${config_file})"
echo -n "${result}"
}
###
# void installation_check(char *config_file)
#
# Check if the pdnsd is installed and can be configured. Prepare also the file
# for resolvconf.
#
installation_check() {
local config_file="$1"
if [[ -e ${config_file} ]] ; then
if ! grep ${INSTALLATION_CHECK} ${config_file} &>/dev/null ; then
echo -e "${COMMENT}" >> ${config_file}
echo -e "\n${BASIC_SETTINGS}" >> ${config_file}
fi
return 0
else
return 1
fi
}
###
# void initialization(char *config_file)
#
# Setup basic variables NAMESERVERS, DOMAINS an CONFIGURED_DOMAINS
#
initialization() {
local config_file="$1"
for N in ${NEWNS} ; do
NAMESERVERS="${NAMESERVERS} ${N}"
done
for N in ${NEWSEARCH} ; do
NAMESERVERS="${NAMESERVERS} ${N#*,}"
done
for DN in ${NEWDOMAIN} ; do
DOMAINS="${DOMAINS} ${DN%,*}"
done
CONFIGURED_DOMAINS=$(read_configured_domains ${config_file})
NAMESERVERS=$(uniqify ${NAMESERVERS})
DOMAINS=$(uniqify ${DOMAINS})
CONFIGURED_DOMAINS=$(uniqify ${CONFIGURED_DOMAINS})
}
###
# void find_changed_and_added(char *configured, char *domains)
#
# Find already configured and newly added domains. Sets variables
# CHANGE_DOMAINS, ADD_DOMAINS and KNOWN_DOMAINS.
#
find_changed_and_added() {
local configured="$1" domains="$2" x
KNOWN_DOMAINS="${CONFIGURED_DOMAINS} ${DOMAINS}"
# Find what has to be disabled
for x in ${configured} ; do
if [[ " ${domains} " != *" ${x} "* ]] ; then
CHANGE_DOMAINS="${CHANGE_DOMAINS} ${x}"
fi
done
# Find what has to be added
for x in ${domains} ; do
if [[ " ${configured} " != *" ${x} "* ]] ; then
ADD_DOMAINS="${ADD_DOMAINS} ${x}"
else
CHANGE_DOMAINS="${CHANGE_DOMAINS} ${x}"
fi
done
ADD_DOMAINS=$(uniqify ${ADD_DOMAINS})
CHANGE_DOMAINS=$(uniqify ${CHANGE_DOMAINS})
KNOWN_DOMAINS=$(uniqify ${KNOWN_DOMAINS})
}
###
# bool make_configuration_change(char *config_file, char *backup_suffix,
# char *sed_script)
#
# Applies any configuration change. Returns true, if there was a change.
#
make_configuration_change() {
local config_file="$1" backup_suffix="$2" sed_script="$3"
local old_config new_config
old_config=$(< ${config_file})
# Sanity check: add '}' at the end of the file
new_config=$( (echo -n "${old_config}" && echo -ne "\n}" ) | \
sed -nre "${sed_script}")
# Now remove what we added
new_config=${new_config%?\}}
if [[ "${old_config}" != "${new_config}" ]] ; then
cp ${config_file} ${config_file}${backup_suffix}
echo "${new_config}" > "${config_file}"
return 0
else
return 1
fi
}
################################################################################
# Main part
# Check, if pdnsd configuration file is installed and possibly prepare it
installation_check "${PDNSDCONFIG}" || exit 0
# Basic initialization of NAMESERVERS, DOMAINS and CONFIGURED_DOMAINS
initialization "${PDNSDCONFIG}"
find_changed_and_added "${CONFIGURED_DOMAINS}" "${DOMAINS}"
sed_script="$(build_sed_script "${NAMESERVERS}" "${NEWDOMAIN}" \
"${CHANGE_DOMAINS}" "${ADD_DOMAINS}" \
"${DOMAINS}" "${KNOWN_DOMAINS}")"
# Check if the config changed
if make_configuration_change "${PDNSDCONFIG}" "${BACKUPSUFFIX}" "${sed_script}" ; then
# Checks for running pdnsd
[ -x /usr/sbin/pdnsd-ctl ] || exit 0
[ -e /var/cache/pdnsd/pdnsd.status ] || exit 0
# Reload config files
/usr/sbin/pdnsd-ctl config &>/dev/null
fi
exit 0
|