2007年6月4日星期一

Building and configuring BIND 9

怎样建造一个BIND 9 DNS

This has turned out to be a damned hard document to write: we work on it bits and pieces at a time. Sorry if it's incomplete. All of our main work has been done on various flavors of Red Hat Linux, but we've included notes on porting to other systems as well.
We're particularly fond of the outstanding O'Reilly DNS and BIND book.

But our goal is to make this a one-stop place to figure how to do do this, and we'd be really grateful if those that were stuck could send us suggestions to clarify. Kindly forward them to .

Introduction
There are plenty of people who've written about how to run BIND in a chroot jail, and we'll add our own experiences. We have done this on a handful of machines and have the routine down pretty well, and anybody else with the same problem set might find this helpful.
We've previously run BIND 8 in a jail, and it has always been a horrid nightmare to build and configure because the install paths had to be hacked up on a custom basis, and every operating system put files in different places. BIND 9 has changed this and decide that it all goes into /usr/local. This has made an enormous difference to consultants with widely varied customer bases. Thank you, ISC.

Most of our direct experience is with various flavors of Red Hat Linux, but we've set this up on Debian's "woody" release as well. These instructions are current as of BIND 9.2.2rc1.

Quick links:

Pick up & unpack the source
Configure and build
Create the jail
Create the configuration files
Starting the nameserver
Daemon control with rndc
Starting named at boot time
Adding local slave zones
Adding local master zones
Remote RNDC
Views / Split DNS
Files created by "make install"
Porting issues
Pick up the source
Get the source at the Internet Software Consortium, and the home page for BIND is http://www.isc.org/products/BIND/. These instructions were written for 9.1.2 on a Red Hat Linux 6.2 system, and we'll try to keep them updated as we upgrade our and customer systems. We generally try to keep running the latest stable versions - we're not generally too adventurous with beta. Our practice is to keep our own build stuff under a /source tree, and to unpack individual sources under it.
# cd /source
# gtar -xzvf bind-9.1.2.tar.gz
This unbundles everything into a subdirectory with the full name of the package, and the next step is to to configure and build it.
Configure and build
The BIND instructions say to simply run ./configure, but under Linux a couple of additions are required.
Apparently, thread support doesn't work right in many kernels (see the FAQ file in the source tree), and we just aren't up to installing a custom kernel when it's so much easier to just disable threading. The notes apply to the 2.2.x kernels, and we don't know if this is fixed in 2.4. Those with stronger stomaches or enormous DNS needs are welcome to Do It Right.

In addition, BIND can support IPv6, the next generation IP addresses (current version is IPv4). BIND typically probes for IPv6 support at runtime, but since we are quite sure that we really don't need this on our networks, we disable it entirely as a safety measure.

NOTE: we prefer to remove any existing nameserver installations (especially those provided by the operating system) before installing the new one. This avoids problems with older versions of key binaries lying around and sometimes being at the wrong point in the command-search $PATH.

Under Red Hat Linux (for instance), this means removing three packages before doing installations.

# rpm -e bind bind-utils caching-nameserver

Finally, we want everything installed into the /usr/local hierarchy, so we provide the installation prefix. This said, configuration and installation is quite simple:

# cd /source/bind-9.1.2

# ./configure --prefix=/usr/local --disable-ipv6 --disable-threads

# make

# make install

This takes about 15 minutes on a dual-CPU 200 MHz Pentium Pro machine and about three minutes on a 1 GHz Pentium III, and it installs around 200 files under /usr/local. Most of them are #include files for C language programming, and only about a dozen are really needed for a BIND installation. See the complete file list at the end of this document.
Build and configure the jail
Creating the actual jail itself is much easier than for BIND 8 because so much trash is not required - it's just tremendous. In particular, none of the shared libraries or named binary files are required to live in the jail, and this makes it easier and more secure for us. For more details on our thoughts on chroot operations, see our more detailed tech tip Best Practices for UNIX chroot() Operations
The initial steps to configure the jail are:

create initial named user and group
# groupadd named

# useradd -g named -d /chroot/named -s /bin/true named

# passwd -l named "lock" the account

Remove all the login-related trash under the newly-created home directory
# rm -rf /chroot/named

Re-create the top level jail directory
# mkdir -p /chroot/named
# cd /chroot/named

create the hierarchy
# mkdir dev
# mkdir etc
# mkdir logs
# mkdir -p var/run
# mkdir -p conf/secondaries

create the devices, but confirm the major/minor device
numbers with "ls -lL /dev/zero /dev/null /dev/random"
# mknod dev/null c 1 3
# mknod dev/zero c 1 5
# mknod dev/random c 1 8

copy the timezone file
# cp /etc/localtime etc

Noticeably absent are any ownership/permissions issues: this is deliberate. We'll get to it shortly. Note that the ls command used to verify the major/minor device numbers includes the -L parameter - this follows symbolic links and is required on some platforms such as Solaris.
Construct the configuration files
The named.conf configuration is central to named operation, and we'll go through creating it step by step. Though these files can get very complex, our first efforts will be for a minimal caching-only nameserver just to get the whole end-to-end process working correctly. Then we'll retrofit to add in needed features such as local domains and access controls.
The named.conf file lives in the jail's etc directory, which makes the full path /chroot/named/etc/named.conf. We usually create a symbolic link to make this visible to the rest of the system at /etc/named.conf:

# ln -s /chroot/named/etc/named.conf /etc/named.conf

Now, using your favorite editor, create the first version of the etc/named.conf file. We suspect that some of these parameters are not strictly necessary, as the defaults will do, but we feel that being explicit will make the daemon easier to debug for the new administrator (less searching for key files)
--------------------------------------------------------------------------------
options {
directory "/conf";
pid-file "/var/run/named.pid";
statistics-file "/var/run/named.stats";
dump-file "/var/run/named.db";

# hide our "real" version number
version "[secured]";
};

# The root nameservers
zone "." {
type hint;
file "db.rootcache";
};

# localhost - forward zone
zone "localhost" {
type master;
file "db.localhost";
notify no;
};

# localhost - inverse zone
zone "0.0.127.in-addr.arpa" {
type master;
file "db.127.0.0";
notify no;
};
--------------------------------------------------------------------------------
Notice that the directory keyword says /conf, not /chroot/named/conf - this is intended. When running the nameserver inside the chroot jail, all the paths are relative to the top of the jail at /chroot/named.
This configuration refers to three additional files -- db.rootcache, db.localhost and db.127.0.0 -- and they are all created in the /chroot/named/conf directory.

db.rootcache is a list of the roughly dozen "root servers" which are the starting points for virtually every name query done throughout the internet, and the latter describes the "localhost" address. Creating conf/db.rootcache file can usually be done automatically by querying the root nameservers directly. If the current machine has working nameservers (say, via your ISP), you can just do the command:

# dig @a.root-servers.net . ns > /chroot/named/conf/db.rootcache

In the absense of any working nameservice, a recent version of the file can be found here.
The two other required files serve to administrate the "localhost" address, and the files are static and need not really be understood:

--------------------------------------------------------------------------------
;
; db.localhost
;
$TTL 86400

@ IN SOA @ root (
42 ; serial (d. adams)
3H ; refresh
15M ; retry
1W ; expiry
1D ) ; minimum

IN NS @
IN A 127.0.0.1
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
;
; db.127.0.0
;
$TTL 86400
@ IN SOA localhost. root.localhost. (
1 ; Serial
28800 ; Refresh
14400 ; Retry
3600000 ; Expire
86400 ) ; Minimum
IN NS localhost.
1 IN PTR localhost.
--------------------------------------------------------------------------------
They should be created one time, and thereafter won't ever be administered again.
Verifying permissions
Now we've created the files required inside the jail, but the matter of setting the permissions and ownership remains. It's possible to do this by hand, but unreliable: it's very hard to "keep up" with making sure that everything is set correctly on an ongoing basis. So we typically create a small shell script that will run through the entire jail and affirmatively set everything.
We typically put this in /chroot/named.perms - it lives outside the jail itself - and we've found that the same file has been usable without change on all of our installations.

--------------------------------------------------------------------------------#
# named.perms
#
# Set the ownership and permissions on the named directory
#

cd /chroot/named


# By default, root owns everything and only root can write, but dirs
# have to be executable too. Note that some platforms use a colon
# instead of a dot between user/group in the chown parameters}

chown -R root.named .

find . -type f -print | xargs chmod u=rw,og=r # regular files
find . -type d -print | xargs chmod u=rwx,og=rx # directories

# the named.conf and rndc.conf must protect their keys
chmod o= etc/*.conf

# the "secondaries" directory is where we park files from
# master nameservers, and named needs to be able to update
# these files and create new ones.

touch conf/secondaries/.empty # placeholder
find conf/secondaries/ -type f -print | xargs chown named.named
find conf/secondaries/ -type f -print | xargs chmod ug=r,o=

chown root.named conf/secondaries/
chmod ug=rwx,o= conf/secondaries/

# the var/run business is for the PID file
chown root.root var/
chmod u=rwx,og=x var/

chown root.named var/run/
chmod ug=rwx,o=rx var/run/

# named has to be able to create logfiles
chown root.named logs/
chmod ug=rwx,o=rx logs/


--------------------------------------------------------------------------------

The "placeholder" file simply insures that the secondaries directory is not empty, and it prevents the script from generating (harmless) error messages.
It's absolutely necessary to run this at least once after setting things up, and periodically whenever changes are made. The command to run this is sh /chroot/named.perms, but the -x option can be added if you want to watch it run:

# sh -x /chroot/named.perms
+ cd /chroot/named
+ chown -R root.named .
+ find . -type f -print
+ xargs chmod u=rw,og=r
+ find . -type d -print
+ xargs chmod u=rwx,og=rx
+ chmod o= etc/named.conf etc/rndc.conf
+ touch conf/secondaries/.empty
+ find conf/secondaries/ -type f -print
+ xargs chown named.named
+ find conf/secondaries/ -type f -print
+ xargs chmod ug=r,o=
+ chown root.named conf/secondaries/
+ chmod ug=rwx,o= conf/secondaries/
+ chown root.root var/
+ chmod u=rwx,og=x var/
+ chown root.named var/run/
+ chmod ug=rwx,o=rx var/run/

A few notes on the files in the jail:

It's not clear that splitting up the etc/ and conf/ directories is necessary, but we've done it this way for a long time and it seems to be a habit. We could probably put everything in conf/ (or etc/) with no impact on safety.

The conf/secondaries/ directory holds the transferred zone files received from the master nameservers, and they are in a separate place for two reasons. One, it helps separate files that you're allowed to modify (the master files) from those that you're not (the secondary).
But more importantly, even the nameserver user itself should not modify your master files, which could be possible if a vulnerability allowed a remote bad guy to run arbitrary code. If the db.master.com files or its directory is writable, the bad guy could hijack a domain this way (with substantial effort). By putting the secondaries in a writable area, it limits the damage that can occur this way and helps keep things organized well.


named needs to puts its process ID somewhere, and this is usually in some variant of /var/run/named.pid. This name is mentioned in the named.conf file, recalling that it's relative to the jail's top directory:
options {
pid-file "/var/run/named.pid";
...
};


The etc/named.conf and etc/rndc.conf files will soon both contain the secret key used to manage the nameserver, and this key must be protected from disclosure by making both files unreadable by anybody other than root or the named group. This is important.
Starting the nameserver
We're just about ready to try starting the nameserver, but since the daemon requires several key parameters - that we cannot omit - we prefer to put the full command in a script file. This small script is placed in /chroot/named.start:
--------------------------------------------------------------------------------
#
# named.start
#
# Note: the path given to the "-c" parameter is relative
# to the jail's root, not the system root.
#
# Add "-n2" if you have multiple CPUs
#
# usage: named [-c conffile] [-d debuglevel] [-f|-g] [-n number_of_cpus]
# [-p port] [-s] [-t chrootdir] [-u username]

cd /chroot/named

# make sure the debugging-output file is writable by named
touch named.run
chown named.named named.run
chmod ug=rw,o=r named.run

PATH=/usr/local/sbin:$PATH named \
-t /chroot/named \
-u named \
-c /etc/named.conf
--------------------------------------------------------------------------------
and made executable with chmod a+x /chroot/named.start. We will never start the nameserver with just a "named" command - we must use the script. So let's do so:
# sh /chroot/named.start

If all is well, the nameserver will start running quietly, and the ps -fCnamed command may show it running in the background. It should now be ready to accept nameserver requests, and we can test it with the dig command. Though we'll need to modify the file /etc/resolv.conf to contain the local machine's address,
Daemon control with rndc
Very old BIND nameservers relied on UNIX signals to control their behavior, and this has always been a lousy mechanism. BIND 8 introduced a ndc command that communicated over a control channel (a UNIX domain socket), but BIND 9 is now doing this via a TCP socket. This allows for remote operation (say, reloading it) of the nameserver. The old ndc command is gone, replaced with rndc, though not all of the commands are implmented yet.
Configuring rndc is a little tricky: BIND supports substantial functionality that involves the use of keys, and rndc uses just a small part of it. This is aggravated by the fact that getting even a little bit of this wrong will cause the mechanism to fail without meaningful diagnostics. It's been very frustrating.

The rndc command reads the file /usr/local/etc/rndc.conf for its configuration data, but we prefer to locate this file under our chroot area to keep an eye on the permissions and ownership. We'll create a link between the two shortly, but we prefer to build the file first.

--------------------------------------------------------------------------------
#
# /chroot/named/etc/rndc.conf
#

options {
default-server 127.0.0.1;
default-key "rndckey";
};

server 127.0.0.1 {
key "rndckey";
};

key "rndckey" {
algorithm "hmac-md5";
secret "secret key here";
};
--------------------------------------------------------------------------------
As with named.conf, the format is very peculiar and requires that all the semicolons go in the right places. In this file, the token "rndckey" is just the name of this key (as opposed to those keys required for other purposes), and any word could be used as long as they all agree in this file.
The variable part is the "secret", which is a long string of base-64-encoded data, and the BIND distribution provides a mechanism for creating one of these keys. The dnssec-keygen is used for generating multiple kinds of keys, and in our case we just care about generating one of them. We'll create the key into a file, then copy that key to our config file:

# cd /chroot/named/etc

# /usr/local/sbin/dnssec-keygen -a HMAC-MD5 -b 256 -n HOST rndc
Krndc.+157+13856

# cat Krndc.+157+13856.private
Private-key-format: v1.2
Algorithm: 157 (HMAC_MD5)
Key: hU9utBAdP6/dVKKfxOlv0bPOTnAd4A1qosMbs/dwVJI=

...

# rm Krndc.+157+13856.* after key has been saved

Odd note: we've seen the dnssec-keygen program simply hang for long periods of time on even very fast machines, and upon investigation found that there was no available random numbers from the /dev/random device (!). Apparently the system collects "randomness" into a pool, and when it's depleted for whatever reason, it waits for more to show up. To get around this, add the -r /dev/urandom option to the command line just before the -a option: this pulls from a different device that won't block on depleted entropy.
The Krndc.+157+13856 is the name of the key, and we don't believe that any part of this name is interesting to us. The key itself is created into output filenames based on the key name: kname.key and kname.private. We see above the contents of the private key file, and the key itself is hU9utBAdP6/dVKKfxOlv0bPOTnAd4A1qosMbs/dwVJI=. This data must be inserted into the rndc.conf file:
...
key "rndckey" {
algorithm "hmac-md5";
secret "hU9utBAdP6/dVKKfxOlv0bPOTnAd4A1qosMbs/dwVJI=";
};

Now the rndc.conf file is created, the "key" files created by dnssec-keygen can be deleted. We also wish to make this config file visible to the rndc program directly: it's looking in /usr/local/etc/rndc.conf for its configuration information. To do this, we perform a symbolic link:
# ln -s /chroot/named/etc/rndc.conf /usr/local/etc/rndc.conf
# ln -s /chroot/named/etc/rndc.conf /etc/rndc.conf

Note that we also have /etc/rndc.conf point to the real file: this is simply as a convenience for the administrator who has to edit this file often - it's easier to type.
Now, the nameserver itself must be configured to listen on a control channel and use this particular key. In the same /chroot/named/etc directory, we reconsider the named.conf file: We must add two sections to the beginning of this file. We add a controls section that describes the network addresses that named will listen on, and a key section describes the key it will use. Copy the secret key from above into the file in the obvious place:

--------------------------------------------------------------------------------
controls {
inet 127.0.0.1 allow { 127.0.0.1; } keys { rndckey; };
};

key "rndckey" {
algorithm "hmac-md5";
secret "hU9utBAdP6/dVKKfxOlv0bPOTnAd4A1qosMbs/dwVJI=";
};
...
--------------------------------------------------------------------------------
Note - previous versions of this document have suggested that the actual key name doesn't matter, but this has proven to be false. Once a key has a name, everybody has to agree on what that name is or things will fail outright.
The controls and key sections must be first in the file: we're not sure why it's a rule, but we're sure it is a rule.

This configuration tells named to listen only on the localhost interface, and as such won't accept any connections from the rest of the network. This is a useful security precaution, though it's possible to "open" the nameserver to accept connections from trusted others. Both of these config files contain these important private keys, so they must not be readable to nonprivileged users.

Now that the keys and controls have been set up properly, it's necessary to kick the nameserver to force it to reread the file. Simply send a SIGHUP (a -1) to the nameserver

# ps -fCnamed
UID PID PPID C STIME TTY TIME CMD
named 12527 1 0 12:42 ? 00:00:00 named -t /chroot/named {...}

# kill -1 12527 12527 is process ID

Now the nameserver should have reread the configuration files and started listening on the control interface. Now let's try rndc:
# /usr/local/sbin/rndc status
number of zones: 2
debug level: 0
xfers running: 0
xfers deferred: 0
soa queries in progress: 0
query logging is OFF
server is up and running

This indicates that all is well: the keys are correct. But a common error seen from a bad configuration is
rndc: send remote authenticator: permission denied

This is often the only diagnostics seen, and it means you have to doublecheck your configuration files. We're sorry that we can't offer much more useful guidance on this front. We'll add notes as we find them.
Starting named at boot time
Now that named is running correctly after being started "by hand", we usually wish for it to start automatically at boot time. The mechanism for this depends somewhat on the particular operating system, but we can give some overall guidelines. Automatic starting at boot time requires a base "init" file plus a couple of symbolic links.
The "base" startup file a small shell script that can start or stop the nameserver, and our version looks like

--------------------------------------------------------------------------------
#!/bin/sh
#
# named
#

export PATH=/usr/local/sbin:$PATH # needed for rndc

case "$1" in
start)
# Start daemons.
echo -n "Starting named: "
sh /chroot/named.start
echo
;;
stop)
# Stop daemons.
echo -n "Shutting down named: "
rndc stop
echo "done"
;;
esac

exit 0
--------------------------------------------------------------------------------
We only support start and stop commands, and this has been more than good enough for us: those wanting a more "full featured" control file are welcome to be adventurous. Be sure to make the file executable: chmod a+x named.
Once the startup script is in place, we have to make some symbolic links to make it start in the appropriate runlevels. The filenames are modified from the "base" name to account for the order in which things are started: we want the nameserver to start just after the IP subsystem has started because so many of the other daemons depend on nameservice.

Locating this init file depends on the operating system, and we'll make notes in this table as we learn about them.

Operating System startup script runlevel 2 file runlevel 3 file
Red Hat Linux 6.X / 7.X /etc/rc.d/init.d/named /etc/rc.d/rc2.d/S11named /etc/rc.d/rc3.d/S11named
Debian "Woody" /etc/init.d/named /etc/rc2.d/S18named /etc/rc3.d/S18named


To create the "rc" files, we use symbolic links. This sample is for the Red Hat Linux organization, and it can be altered for your operating system. We also believe that other runlevels are used for things like X11 windowing, but we don't ever use them. Your mileage may vary.

# cd /etc/rc.d
# ln -s ../init.d/named rc2.d/S11named
# ln -s ../init.d/named rc3.d/S11named

We recommend rebooting the system once to be sure that the nameserver starts properly on an automatic basis: it's very frustrating to have a power outage (say) three months from now and find out that DNS is not available: better to test this while your mind is thinking about nameservice.
Adding local slave zones
(to be determined)
Adding local master zones
(to be determined)
Remote RNDC
The nameserver as installed only accepts rndc requests from the local system, but it's possible to do so over the network with a few changes in the config file.
The change is made in the named.conf file, and we add an entry to the controls section. The addition is made here in bold:

controls {
inet 127.0.0.1 allow { 127.0.0.1; } keys { rndckey; };

inet 192.168.1.31 allow { 127.0.0.1; # localhost
192.168.1.0/24; # local Ethernet
10.1.2.0/24; # network at home
172.27.217.6; # our consultant
} keys { rndckey; };
};

...


The first inet line requests that named listed on the localhost interface only, but the second requests that it listen on the computer's public Ethernet address (here, 192.168.1.31). The access control lines limit the connections to addresses in the given list, and we can include as many as desired in either single IP address notation or /nbits netbits notation. Be careful to put semicolons in all places that matter - it's easy to get wrong.
Now the config files must be reread: but if we're adding a new interface to listen on, we have to fully stop and restart the daemon. Since we've configured it to run as a non-root user, it's simply not able to bind to the privileged port (953/tcp) on the additional interface. So we must stop and restart.

Now to the remote machine.

On some other machine that's in the access list of the nameserver to be controlled, we must modify rndc.conf to add the keys. We presume here that the rndc.conf file will be controlling the local machine as well as the remote ones, so we'll be adding to the file, not modifying it.

#
# /chroot/named/etc/rndc.conf
#

options {
default-server 127.0.0.1;
default-key "rndckey";
};

server 127.0.0.1 {
key "rndckey";
};

key "rndckey" {
algorithm "hmac-md5";
secret "hU9utBAdP6/dVKKfxOlv0bPOTnAd4A1qosMbs/dwVJI=";
};

server remote.example.com {
key "remotekey";
};

key "remotekey" {
algorithm "hmac-md5";
secret "4TT2RNenA3JyHJAVHvWQTzgOo8GzqHowHUdB2i95peM=";
};

NOTE: we use 127.0.0.1 instead of localhost above because the latter requires name resolution and the former doesn't. We've seen cases where we messed up the "localhost" resource record and caused rndc to stop working. This obviates that problem.
Here, the entry for the remote computer includes a definition of the key used by that remote, and it's clearly different than the key used for the local one.

To control the remote nameserver, we use rndc with the -s servername parameter:

# rndc -s remote.example.com status
number of zones: 13
debug level: 0
xfers running: 0
xfers deferred: 0
soa queries in progress: 0
query logging is OFF
server is up and running

We're able to stop and reload the remote server, but there is no way to restart it via this mechanism. Be careful that you don't get surprised.
Views / Split DNS
It's possible to run BIND 9 in a "split DNS" configuration, where the nameserver will give different answers to the same question depending on who's doing the asking. This is mainly useful for sites that run private networks inside with a limited public footprint on the outside.
We've only barely touched the whole "view" thing and can't offer any real advice in configuration, but we did run into one maddening problem regarding rndc that we'll touch on here.

When configuration changes have been made to the zone or config files, one normally can do rndc reload to load them all, but it's also possible to reload just one zone: this can be useful for very busy systems.

But when views are used, it fails in a very unhelpful way;

# rndc reload unixwiz.net
rndc: 'reload' failed: not found

After fooling around with it for a while we realized that the command requires additional parameters: the class and view names. So we figured out that it's done this way:
# rndc reload unixwiz.net IN external

This requests the "internet" ( IN ) class and the external view: apparently these are not the defaults. We suspect that there are ways around this (say, by naming the view something else), but we very much think that the message from rndc was not very helpful. We might look into some patches to the 9.2.0 source to expand on this to provide a bit more feedback. It was very frustrating.
Files created by "make install"
One of the really ugly problems with previous versions of BIND is that the various important files got scattered all over the filesystem, and the locations varied by platform as determined by the operating system vendor. It was just a nightmare to configure a chroot nameserver for a new platform.
BIND 9 makes this dramatically easier because they have decided to put everything under one place (usually /usr/local/), but there are enough files installed that it can be a bit overwhelming to know just what you need.

This is a list of files that were installed by our own configuration of BIND 9, and it might help you decide what you need to take for a binary-only distribution. Note that this was as of an early BIND 9 installation: we've not updated this since we have upgraded our own installation.

Porting Issues
SCO UNIX 3.2v5.0.5 (OpenServer 5)
We've had all kinds of trouble building BIND 9.2.1 on SCO Open Server wtih gcc 2.7.2.2, though we think we've gotten around it. We have had zero luck with the stock C compiler (no "long long" support). Note for this platform can be found here.

Solaris
So that file-based logfiles have the correct time zone (as opposed to GMT), insure that the jail contains copies of the time-zone definition files found in /usr/share/lib/zoneinfo/US/*. They probably should be owned by root and be unwritable by all.
Solaris and *BSD use a colon instead of a dot between the user name and the group name in the chown command. *BSD uses wheel as the main root group, and Solaris uses other.

0 评论: