root/alternc/tags/0.9.6.3/src/update_domains.sh

Revision 1772, 14.8 kB (checked in by anarcat, 2 years ago)

Fix a race condition in update_domaines.sh that could allow users to
bypass open_basedir protections when creating domains. Reported by
jerome.

This required changing basedir_prot's behavior so that it creates the
protection even if the symlink doesn't exist, which might create extra
files that are not necessary, but "better be safe than sorry". We
could also fix this in a subsequent release.

  • Property svn:executable set to *
Line 
1 #!/bin/sh
2 #
3 # $Id: update_domaines.sh,v 1.31 2005/08/29 19:21:31 anarcat Exp $
4 # ----------------------------------------------------------------------
5 # AlternC - Web Hosting System
6 # Copyright (C) 2002 by the AlternC Development Team.
7 # http://alternc.org/
8 # ----------------------------------------------------------------------
9 # Based on:
10 # Valentin Lacambre's web hosting softwares: http://altern.org/
11 # ----------------------------------------------------------------------
12 # LICENSE
13 #
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License (GPL)
16 # as published by the Free Software Foundation; either version 2
17 # of the License, or (at your option) any later version.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 # GNU General Public License for more details.
23 #
24 # To read the license please visit http://www.gnu.org/copyleft/gpl.html
25 # ----------------------------------------------------------------------
26 # Original Author of file: Jerome Moinet for l'Autre Net - 14/12/2000
27 # Purpose of file: system level domain management
28 # ----------------------------------------------------------------------
29 #
30
31 PATH=/sbin:/bin:/usr/sbin:/usr/bin
32
33 set -e
34
35 umask 022
36
37 ########################################################################
38 # Constants & Preliminary checks
39 #
40
41 CONFIG_FILE="/etc/alternc/local.sh"
42
43 DOMAIN_LOG_FILE="/var/log/alternc/update_domains.log"
44 DATA_ROOT="/var/alternc"
45
46 NAMED_TEMPLATE="/etc/bind/templates/named.template"
47 ZONE_TEMPLATE="/etc/bind/templates/zone.template"
48
49 ACTION_INSERT=0
50 ACTION_UPDATE=1
51 ACTION_DELETE=2
52 TYPE_LOCAL=0
53 TYPE_URL=1
54 TYPE_IP=2
55 TYPE_WEBMAIL=3
56 YES=1
57
58 if [ `id -u` -ne 0 ]; then
59     echo "update_domains.sh must be launched as root"
60     exit 1
61 fi
62
63 if [ ! -x "/usr/bin/get_account_by_domain" ]; then
64     echo "Your AlternC installation is incorrect ! If you are using pre 0.9.4, "
65     echo "you have to install alternc-admintools: "
66     echo "    apt-get update ; apt-get install alternc-admintools"
67     exit 1
68 fi
69
70 if [ ! -r "$CONFIG_FILE" ]; then
71     echo "Can't access $CONFIG_FILE."
72     exit 1
73 fi
74
75 . "$CONFIG_FILE"
76
77 if [ -z "$MYSQL_HOST" -o -z "$MYSQL_DATABASE" -o -z "$MYSQL_USER" -o \
78      -z "$MYSQL_PASS" -o -z "$DEFAULT_MX" -o -z "$PUBLIC_IP" ]; then
79     echo "Bad configuration. Please use:"
80     echo "   dpkg-reconfigure alternc"
81     exit 1
82 fi
83
84 if [ -f "$LOCK_FILE" ]; then
85     echo "`date` $0: last cron unfinished or stale lock file." |
86         tee -a "$DOMAIN_LOG_FILE" >&2
87     exit 1
88 fi
89
90 NAMED_CONF_FILE="$DATA_ROOT/bind/automatic.conf"
91 ZONES_DIR="$DATA_ROOT/bind/zones"
92 APACHECONF_DIR="$DATA_ROOT/apacheconf"
93 OVERRIDE_PHP_FILE="$APACHECONF_DIR/override_php.conf"
94 WEBMAIL_DIR="$DATA_ROOT/bureau/admin/webmail"
95 LOCK_FILE="$DATA_ROOT/bureau/cron.lock"
96 HTTP_DNS="$DATA_ROOT/dns"
97 HTML_HOME="$DATA_ROOT/html"
98
99 MYSQL_SELECT="mysql -h${MYSQL_HOST} -u${MYSQL_USER}
100                     -p${MYSQL_PASS} -Bs ${MYSQL_DATABASE}"
101 MYSQL_DELETE="mysql -h${MYSQL_HOST} -u${MYSQL_USER}
102                     -p${MYSQL_PASS} ${MYSQL_DATABASE}"
103
104 ########################################################################
105 # Functions
106 #
107
108 print_domain_letter() {
109     local domain="$1"
110
111     local letter=`echo "$domain" | awk '{z=split($NF, a, ".") ; print substr(a[z-1], 1, 1)}'`
112     if [ -z "$letter" ]; then
113       letter="_"
114     fi
115     echo $letter
116 }
117
118 print_user_letter() {
119     local user="$1"
120
121     echo "$user" | awk '{print substr($1, 1, 1)}'
122 }
123
124 add_to_php_override() {
125     local fqdn="$1"
126
127     /usr/lib/alternc/basedir_prot.sh "$fqdn" >> "$DOMAIN_LOG_FILE"
128 }
129
130 remove_php_override() {
131     local fqdn="$1"
132     local letter=`print_domain_letter $fqdn`
133
134     sed -i "/$fqdn/d" $APACHECONF_DIR/override_php.conf
135     rm -f $APACHECONF_DIR/$letter/$fqdn
136 }
137
138 add_to_named_reload() {
139     local domain="$1"
140     local escaped_domain=`echo "$domain" | sed -e 's/\./\\\./g'`
141
142     if [ "domain" = "all" ] || grep -q "^all$" "$RELOAD_ZONES_TMP_FILE"; then
143         echo "all" > "$RELOAD_ZONES_TMP_FILE"
144     else
145         if ! grep -q "^${escaped_domain}$" "$RELOAD_ZONES_TMP_FILE"; then
146             echo "$domain" >> "$RELOAD_ZONES_TMP_FILE"
147         fi
148     fi
149 }
150
151 # we assume that the serial line contains the "serial string", eg.:
152 #                 2005012703      ; serial
153 #
154 # returns 1 if file isn't readable
155 # returns 2 if we can't find the serial number
156 # returns 3 if a tempfile can't be created
157 increment_serial() {
158     local domain="$1"
159     local zone_file="$ZONES_DIR/$domain"
160     local current_serial
161     local new_serial
162     local date
163     local revision
164     local today
165
166     if [ ! -f "$zone_file" ]; then
167         return 1
168     fi
169
170     # the assumption is here
171     current_serial=`awk '/^..*serial/ {print $1}' < "$zone_file"` || return 2
172     if [ -z "$current_serial" ]; then
173         return 2
174     fi
175
176     date=`echo $current_serial | cut -c1-8`
177     revision=`echo $current_serial | sed s/"${date}0\?"/""/g`
178     today=`date +%Y%m%d`
179     # increment the serial number only if the date hasn't changed
180     if [ "$date" = "$today" ] ; then
181         revision=$(($revision + 1))
182     else
183         revision=1
184         date=$today
185     fi
186     new_serial="$date`printf '%.2d' $revision`"
187
188     # replace serial number
189     cp -a -f "$zone_file" "$zone_file.$$"
190     awk -v "NEW_SERIAL=$new_serial" \
191         '{if ($3 == "serial")
192              print "            "NEW_SERIAL "   ; serial"
193           else
194              print $0}' < "$zone_file" > "$zone_file.$$"
195     mv -f "$zone_file.$$" "$zone_file"
196
197     add_to_named_reload "$domain"
198
199     return 0
200 }
201
202 change_host_ip() {
203     local domain="$1"
204     local zone_file="$ZONES_DIR/$domain"
205     local ip="$2"
206     local host="$3"
207     local pattern
208     local a_line
209
210     if [ -z "$host" ]; then
211         host="@"
212     fi
213     a_line="$host       IN      A       $ip"
214     pattern="^$host[[:space:]]*IN[[:space:]]*A[[:space:]]*.*\$"
215     if [ ! -f "$zone_file" ]; then
216         echo "Should change $host.$domain, but can't find $zone_file."
217         return 1
218     fi
219     if grep -q "$pattern" "$zone_file"; then
220         cp -a -f "$zone_file" "$zone_file.$$"
221         sed "s/$pattern/$a_line/" < "$zone_file" > "$zone_file.$$"
222         mv "$zone_file.$$" "$zone_file"
223     else
224         echo "$a_line" >> "$zone_file"
225     fi
226     add_to_named_reload "$domain"
227 }
228
229 add_host() {
230     local domain="$1"
231     local host_type="$2"
232     local host="$3"
233     local value="$4"
234     local user="$5"
235     local domain_letter=`print_domain_letter "$domain"`
236     local user_letter=`print_user_letter "$user"`
237     local ip
238     local fqdn
239     local vhost_directory
240        
241     delete_host "$domain" "$host"
242
243     if [ "$host" = "@" -o -z "$host" ]; then
244         FQDN="$domain"
245     else
246         FQDN="$host.$domain"
247     fi
248     if [ "$host_type" != "$TYPE_IP" ]; then
249         add_to_php_override "$FQDN"
250     fi
251
252     if [ "$host_type" = "$TYPE_IP" ]; then
253        ip="$value"
254     else
255        ip="$PUBLIC_IP"
256     fi
257     if [ "$host" = "@" -o -z "$host" ]; then
258         change_host_ip "$domain" "$ip" || true
259         fqdn="$domain"
260     else
261         change_host_ip "$domain" "$ip" "$host" || true
262         fqdn="${host}.${domain}"
263     fi
264
265     vhost_directory="${HTTP_DNS}/${domain_letter}/${fqdn}"
266     htaccess_directory="${HTTP_DNS}/redir/${domain_letter}/${fqdn}"
267
268     case "$host_type" in
269       $TYPE_LOCAL)
270         ln -snf "${HTML_HOME}/${user_letter}/${user}${value}" \
271                 "$vhost_directory"
272         ;;
273
274       $TYPE_WEBMAIL)
275         ln -snf "${WEBMAIL_DIR}" "$vhost_directory"
276         ;;
277
278       $TYPE_URL)
279         mkdir -p "$htaccess_directory"
280         (echo "RewriteEngine on"
281          echo "RewriteRule (.*) ${value}/\$1 [R,L]"
282         ) > "$htaccess_directory/.htaccess"
283         ln -snf "$htaccess_directory" "$vhost_directory"
284         ;;
285        
286       $TYPE_IP)
287         rm -f "$vhost_directory"
288         rm -rf "$htaccess_directory/.htaccess"
289         ;;
290
291       *)
292         echo "Unknow type code: $type" >> "$DOMAIN_LOG_FILE"
293         ;;
294     esac
295 }
296
297 delete_host() {
298     local domain="$1"
299     local host="$2"
300     local domain_letter=`print_domain_letter "$domain"`
301     local fqdn
302     local escaped_host
303     local escaped_fqdn
304    
305     if [ "$host" = "@" -o -z "$host" ]; then
306         fqdn="$domain"
307         escaped_host=""
308     else
309         fqdn="$host.$domain"
310         escaped_host=`echo "$host" | sed 's/\([\*|\.]\)/\\\\\1/g'`
311     fi
312
313     if [ -f "$ZONES_DIR/$domain" ] ; then
314         cp -a -f "$ZONES_DIR/$domain" "$ZONES_DIR/$domain.$$"
315         sed -e "/^$escaped_host[[:space:]]*IN[[:space:]]*A[[:space:]]/d" \
316             < "$ZONES_DIR/$domain" > "$ZONES_DIR/$domain.$$"
317         mv "$ZONES_DIR/$domain.$$" "$ZONES_DIR/$domain"
318         increment_serial "$domain"
319         add_to_named_reload "$domain"
320     fi
321
322     rm -f "$APACHECONF_DIR/$domain_letter/$fqdn"
323
324     escaped_fqdn=`echo "$fqdn" | sed 's/\([\*|\.]\)/\\\\\1/g'`
325
326     cp -a -f "$OVERRIDE_PHP_FILE" "$OVERRIDE_PHP_FILE.$$"
327     sed -e "/\/${escaped_fqdn}\$/d" \
328         < "$OVERRIDE_PHP_FILE" > "$OVERRIDE_PHP_FILE.$$"
329     mv "$OVERRIDE_PHP_FILE.$$" "$OVERRIDE_PHP_FILE"
330
331     rm -f "$HTTP_DNS/$domain_letter/$fqdn"
332     rm -rf "$HTTP_DNS/redir/$domain_letter/$fqdn"
333 }
334
335
336 init_zone() {
337     local domain="$1"
338     local escaped_domain=`echo "$domain" | sed -e 's/\./\\\./g'`
339     local zone_file="$ZONES_DIR/$domain"
340     local serial
341
342     if [ ! -f "$zone_file" ]; then
343         serial=`date +%Y%m%d`00
344         sed -e "s/@@DOMAINE@@/$domain/g;s/@@SERIAL@@/$serial/g" \
345             < "$ZONE_TEMPLATE" > "$zone_file"
346         chgrp bind "$zone_file"
347         chmod 640  "$zone_file"
348     fi
349     if ! grep -q "\"$escaped_domain\"" "$NAMED_CONF_FILE"; then
350         cp -a -f "$NAMED_CONF_FILE" "$NAMED_CONF_FILE".prec
351         sed -e "s/@@DOMAINE@@/$domain/g" \
352                 < "$NAMED_TEMPLATE" >> "$NAMED_CONF_FILE"
353         add_to_named_reload "all"
354     fi
355 }
356
357 remove_zone() {
358     local domain="$1"
359     local escaped_domain=`echo "$domain" | sed -e 's/\./\\\./g'`
360     local zone_file="$ZONES_DIR/$domain"
361
362     if [ -f "$zone_file" ]; then
363         rm -f "$zone_file"
364     fi
365
366     if grep -q "\"$escaped_domain\"" "$NAMED_CONF_FILE"; then
367         cp -a -f "$NAMED_CONF_FILE" "$NAMED_CONF_FILE.prec"
368         cp -a -f "$NAMED_CONF_FILE" "$NAMED_CONF_FILE.$$"
369         # That's for multi-line template
370         #sed -e "/^zone \"$escaped_domain\"/,/^};/d" \
371         # That's for one-line template
372         grep -v "^zone \"$escaped_domain\"" \
373             < "$NAMED_CONF_FILE" > "$NAMED_CONF_FILE.$$"
374         mv -f "$NAMED_CONF_FILE.$$" "$NAMED_CONF_FILE"
375         add_to_named_reload "all"
376     fi
377 }
378
379 change_mx() {
380     local domain="$1"
381     local mx="$2"
382     local zone_file="$ZONES_DIR/$domain"
383     local pattern="^@*[[:space:]]*IN[[:space:]]*MX[[:space:]]*[[:digit:]]*[[:space:]].*\$"
384     local mx_line="@    IN      MX      5       $mx."
385
386     # aller chercher le numéro de la ligne MX
387     # XXX: comportement inconnu si plusieurs matchs ou MX commenté
388     if grep -q "$pattern" "$zone_file"; then
389         cp -a -f "$zone_file" "$zone_file.$$"
390         sed -e "s/$pattern/$mx_line/" < "$zone_file" > "$zone_file.$$"
391         mv "$zone_file.$$" "$zone_file"
392     else
393         echo "$mx_line" >> "$zone_file"
394     fi
395
396     increment_serial "$domain"
397     add_to_named_reload "$domain"
398 }
399
400
401 ########################################################################
402 # Main
403 #
404
405 # Init
406
407 touch "$LOCK_FILE"
408 DOMAINS_TMP_FILE=`mktemp -t alternc.update_domains.XXXXXX`
409 HOSTS_TMP_FILE=`mktemp -t alternc.update_domains.XXXXXX`
410 RELOAD_ZONES_TMP_FILE=`mktemp -t alternc.update_domains.XXXXXX`
411
412 cleanup() {
413     rm -f "$LOCK_FILE" "$DOMAINS_TMP_FILE" "$HOSTS_TMP_FILE"
414     rm -f "$RELOAD_ZONES_TMP_FILE"
415     exit 0
416 }
417
418 trap cleanup 0 1 2 15
419
420 # Query database
421
422 $MYSQL_SELECT <<EOF | tail -n '+1' > "$DOMAINS_TMP_FILE"
423 SELECT membres.login,
424        domaines_standby.domaine,
425        domaines_standby.mx,
426        domaines_standby.gesdns,
427        domaines_standby.gesmx,
428        domaines_standby.action
429   FROM domaines_standby
430        LEFT JOIN membres membres
431                ON membres.uid = domaines_standby.compte
432  ORDER BY domaines_standby.action
433 EOF
434
435 $MYSQL_SELECT <<EOF | tail -n '+1' > "$HOSTS_TMP_FILE"
436 SELECT membres.login,
437        sub_domaines_standby.domaine,
438        if (sub_domaines_standby.sub = '', '@', sub_domaines_standby.sub),
439        if (sub_domaines_standby.valeur = '', 'NULL',
440                                              sub_domaines_standby.valeur),
441        sub_domaines_standby.type,
442        sub_domaines_standby.action
443   FROM sub_domaines_standby
444        LEFT JOIN membres membres
445                ON membres.uid = sub_domaines_standby.compte
446  ORDER BY sub_domaines_standby.action desc
447 EOF
448
449 # Handle domain updates
450
451 if [ "`wc -l < $DOMAINS_TMP_FILE`" -gt 0 ]; then
452     echo `date` >> $DOMAIN_LOG_FILE
453     cat "$DOMAINS_TMP_FILE" >> $DOMAIN_LOG_FILE
454 fi
455
456 # We need to tweak the IFS as $MYSQL_SELECT use tabs to separate fields
457 OLD_IFS="$IFS"
458 IFS="   "
459 while read user domain mx are_we_dns are_we_mx action ; do
460     IFS="$OLD_IFS"
461
462     DOMAIN_LETTER=`print_domain_letter "$domain"`
463     USER_LETTER=`print_user_letter "$user"`
464
465     case "$action" in
466       $ACTION_INSERT)
467         if [ "$are_we_dns" = "$YES" ] ; then
468             init_zone "$domain"
469         fi
470         ;;
471
472       $ACTION_UPDATE)
473         if [ "$are_we_dns" = "$YES" ] ; then
474             init_zone "$domain"
475             change_mx "$domain" "$mx"
476         else
477             remove_zone "$domain"
478         fi
479         ;;
480
481       $ACTION_DELETE)
482         remove_zone "$domain"
483
484         # remove symlinks
485         rm -f "${HTTP_DNS}/${DOMAIN_LETTER}/"*".$domain"
486         rm -f "${HTTP_DNS}/${DOMAIN_LETTER}/$domain"
487         rm -rf "${HTTP_DNS}/redir/${DOMAIN_LETTER}/"*".$domain"
488         rm -rf "${HTTP_DNS}/redir/${DOMAIN_LETTER}/$domain"
489         ;;
490
491       *)
492         echo "Unknown action code: $action" >> "$DOMAIN_LOG_FILE"
493         ;;
494     esac
495
496     IFS="       "
497 done < "$DOMAINS_TMP_FILE"
498 IFS="$OLD_IFS"
499
500 # Handle hosts update
501
502 if [ "`wc -l < $HOSTS_TMP_FILE`" -gt 0 ] ; then
503     echo `date` >> $DOMAIN_LOG_FILE
504     cat "$HOSTS_TMP_FILE" >> $DOMAIN_LOG_FILE
505 fi
506
507 OLD_IFS="$IFS"
508 IFS="   "
509 while read user domain host value type action; do
510     IFS="$OLD_IFS"
511
512     case "$action" in
513       $ACTION_UPDATE | $ACTION_INSERT)
514         add_host "$domain" "$type" "$host" "$value" "$user"
515         ;;
516
517       $ACTION_DELETE)
518         delete_host "$domain" "$host"
519         ;;
520
521       *)
522         echo "Unknown action code: $action" >> "$DOMAIN_LOG_FILE"
523         ;;
524     esac
525
526     IFS="       "
527 done < "$HOSTS_TMP_FILE"
528 IFS="$OLD_IFS"
529
530 # Reload configuration for named and apache
531
532 RELOAD_ZONES=`cat "$RELOAD_ZONES_TMP_FILE"`
533 if [ ! -z "$RELOAD_ZONES" ]; then
534     if [ "$RELOAD_ZONES" = "all" ]; then
535         rndc reload || echo "Cannot reload bind" >> "$DOMAIN_LOG_FILE"
536     else
537         for zone in $RELOAD_ZONES; do
538             rndc reload "$zone" || echo "Cannot reload bind for zone $zone" >> "$DOMAIN_LOG_FILE"
539         done
540     fi
541     apachectl graceful > /dev/null || echo "Cannot restart apache" >> "$DOMAIN_LOG_FILE"
542 fi
543
544 # Cleanup
545
546 echo "DELETE FROM domaines_standby" | $MYSQL_DELETE
547 echo "DELETE FROM sub_domaines_standby" | $MYSQL_DELETE
548
549 # vim: et sw=4
550
Note: See TracBrowser for help on using the browser.