The term client/server has been used and abused for so long that it isn’t all that exciting any more—unless, of course, you’re one of many busy sysadmins who need to provide certain core capabilities to their zillion-user communities. In this case, the idea of setting up centralized servers to satisfy the requirements of many clients isn’t just a buzzword; it’s an efficient use of your time and system resources, and it simplifies administering those services in the future. This chapter provides hacks that discuss setting up centralized services for allocating IP addresses to new clients via the Dynamic Host Configuration Protocol (DHCP), integrating these newly assigned IP addresses with an existing Domain Name Service (DNS), synchronizing the clocks on all of your systems via the Network Time Protocol (NTP), and even sharing a consistent set of X Window System fonts throughout your organization so that all your users can do their status reports using the same version of Computer Modern Ransom Note Oblique.
Another focus of this chapter is on centralizing print services and systems throughout the organization for which you’re responsible. The mechanisms used to print files on different types of systems have traditionally been specific to the operating systems that they use. This was okay when each user had a printer chained to his system with a parallel umbilical cord, or when organizations used only one operating system to get their work done. However, this type of tunnel vision is completely unworkable in today’s networked, heterogeneous computing environments. Luckily, unified printing solutions are now available, thanks largely to Michael Sweet and the other folks at Easy Software Products. Their creation of the Common Unix Printing System (CUPS), which might better be described as the Completely Universal Printing System, provided a powerful, centralized printing system that works everywhere. CUPS can handle and manage print jobs from modern operating systems such as Linux, Microsoft Windows, and Mac OS X to old-school Unix boxes. All you have to know is what to tweak where, why to tweak it, and how to do so. This chapter provides hacks that give you all that information and more.
Take control of DHCP services to better integrate with other tools in your environment.
There are lots of places where clients are running Linux-based services infrastructures in SOHO environments. I do this myself at home. When you’re in a smaller environment, there are lots of black-box appliances and all-in-one software packages that will take care of automatically assigning IP addresses to all the hosts on your network. Some will even let your DNS server know about dynamically assigned addresses, which is great. However, as the environment grows and more services and machines are added, this can get to be somewhat cumbersome.
The first time I realized that I might not want my wireless router giving out IP addresses was when I got a visit from a friend of mine who has a wireless laptop (which happened to be in his truck). While we were talking, he wanted to show me a web site, but he couldn’t remember what it was called. He had it bookmarked on the laptop, so I told him to go get it, and I’d put his MAC address into my wireless router’s “OK” table. Problem was, I had forgotten the password to my router. I had set it up months ago, and none of my formula-derived passwords were working. He wound up saying never mind, and I was really disappointed in that piece of my infrastructure.
After thinking more about that scenario, I realized I shouldn’t even need to touch a black-box appliance to allow a guest to get an IP address in my environment. If I just ran a normal DHCP server on my Linux box, I could let the wireless router do what it’s supposed to do (route wireless traffic), and leave the rest to my Linux server, which is good at doing most other things (heck, if I had wireless PCI cards, it could route the wireless traffic as well!).
Another benefit to using your own DHCP server is that you can add in DHCP options that may not be supported by the appliance. For example, my wireless router will not deliver the IP addresses of NTP servers or NIS servers to my clients, and it won’t tell my PXE-booted clients a filename to go grab from a DHCP server. In fact, it doesn’t even support a “next-server” directive to use for NFS kickstarts of my Red Hat machines. DHCP really can open up a lot of doors to making a SOHO environment less about maintaining technology and more about getting business done!
Of course, before I could do any of these really cool things, I had to set up my own DHCP server. I’ve only ever used the Internet Systems Consortium’s DHCP server, which is the one that comes with just about all Linux distributions, so that’s what I decided to go with. It’s also the one I’ve maintained in much larger production environments, so I know for sure it’s up to whatever task I can throw at it in a SOHO setting.
The first step in this hack is to get the DHCP daemon, dhcpd, installed on your system. On Red Hat Enterprise Linux systems, the up2date utility can be used to install the server and any dependencies, with the following command:
# up2date -i dhcp
On Fedora systems, use yum to do the same thing:
# yum install dhcp
On Debian systems, use the apt-get utility:
# apt-get install dhcp
Debian stable, at the time of writing, provides a somewhat older version of the DHCP daemon, which does not contain support for dynamic DNS updates. Nor does it supply a BIND DNS server that is capable of accepting such updates. If you need this capability, it is suggested that you use the DHCP server in the unstable Debian branch or build from source.
For those building from source, you can grab a source tarball from the ISC web site at http://www.isc.org/index.pl?/sw/dhcp/. To build, the old magic three-command incantation still works:
$./configure
$make
#make install
DHCP is not difficult to configure. We’ll start with simple requirements, and leave more hardcore stuff to the next hack. The configuration file for this service is /etc/dhcpd.conf. The first few lines of this file set up global parameters that apply to all hosts served by this DHCP server:
option domain-name "linuxlaboratory.org"; option subnet-mask 255.255.255.0; deny unknown-clients; option domain-name-servers 192.168.198.50; default-lease-time 600; max-lease-time 7200;
The first line assigns a domain name to our environment, which is fairly arbitrary in a small environment that’s not supporting a registered Internet domain. The subnet-mask
option ensures that everyone has the same subnet mask on your network. This may not be the case at your site, in which case you can specify this parameter in different places in the config file to get the desired effect.
The deny unknown-clients
option keeps the server from providing IP addresses to hosts that are not specified in the configuration file. The default, for some reason, is to allow this activity.
I have but a single host right now in my budding DMZ: my domain name server, which all of my internal hosts use. Rather than configuring its address manually on each host (and having to manually update it if a change is made), I just deliver its address to the clients via DHCP, using the domain-name-servers
directive.
Finally, the lease times are set up such that the default-lease-time
is 600 seconds (10 minutes) and the maximum time a host can go without renewing a lease is 7,200 seconds (2 hours).
Unlike the first section, the next section of the file is not global, but is specific to a subnet. It is befittingly called a “subnet statement,” and you can have as many of these as you have subnets (or more, but hopefully you see that that would hardly make sense). Here’s an entry for my internal network’s subnet:
subnet 192.168.42.0 netmask 255.255.255.0 { range 192.168.42.85 192.168.42.99; option broadcast-address 192.168.42.255; option routers 192.168.42.1; }
Every subnet statement is required to have a netmask
specified, regardless of what’s in the global section of the config file. Inside the braces, the first thing you see is that I’ve set things up so that hosts that are to receive dynamic addresses on this subnet can receive only IP node numbers between 85 and 99. This allows me to have 15 dynamically assigned hosts on my net-work—a good-sized pool for now.
Next, I specify the broadcast-address
for the domain, which is the address for “all hosts” on the subnet. And finally, I always specify a router for each subnet, because every subnet must have its own gateway address. I suppose I could’ve made this a global option in this case, since all of my internal hosts are on the same subnet, but if I then added a subnet (which I will when I break wireless hosts out to their own subnet), I’d have to make changes to different parts of the configuration instead of just adding a new subnet statement.
We’re not done yet, though! You still need to tell the DHCP server about the hosts on your network. This can be a bit of drudgery, as it requires you to know or find out the MAC addresses of all the hosts on your network. When DHCP clients start up, they broadcast to request DHCP service, and you only want your DHCP server to respond to those hosts whose MAC addresses are listed in the configuration file. This beats by a mile the old default wireless gateway configuration of handing out addresses to the entire neighborhood! Here’s a simple, yet fairly typical, host entry in dhcpd.conf:
host gala { hardware ethernet 00:30:65:0f:d8:52; fixed-address 192.168.42.58; }
The host gala happens to be my Apple G4. The two pieces of information I’ve provided are the Ethernet address of the machine and a fixed-address
, which is optional but ensures that gala will always get the exact same IP address every time it renews its lease.
Why the heck would I do that? Doesn’t it defeat the “Dynamic” part of DHCP? Well, in some ways, maybe it does, but there are various reasons why you might do this. First, if you don’t use DNS, you’re likely still using /etc/hosts to resolve other hosts on the network. It would be nice not to have to change these files on each host because a host’s IP address has been changed. Forgetting to do so would be especially bad if that host was, say, the file server where all your important data lives. Likewise, if it’s your desktop IP address that’s changed and you don’t update the hosts file, the file server won’t resolve your hostname correctly and could export your data to someone else!
Even if you are running a DNS server, you still might want DHCP to assign fixed addresses. For example, you may not be able to use dynamic DNS updates for one reason or another. It also helps with troubleshooting: if a host can get different IP addresses at any given time, IP addresses that are not resolved to hostnames in your logs or tcpdump output become meaningless until and unless you track down which host had which address at the time specified in the logs.
That said, it’s completely optional, and you certainly aren’t forced to assign fixed addresses to your hosts. You can mix and match as well—for example, when I add my buddy’s laptop to my new DHCP configuration, I don’t care what IP address he gets, because he’s not going to use any of my in-house services; he’s just going to have Internet access. Here’s the entry for his laptop:
host appio-wireless { hardware ethernet 00:90:4B:6D:97:59; } host appio-wired { hardware ethernet 00:90:3D:93:AD:3E; }
Now he’ll get a randomly assigned address, which of course will be from the 15-address pool specified in my earlier subnet statement. Notice that I added separate entries for his wired and wireless interfaces. You can enter as many “one-liner” entries like this as you want—those entries represent the simplest form of “host” entry. Keep in mind one important tip, though, which is to remember not to assign fixed addresses that overlap with the pool you’ve configured in your subnet statement. For example, if I had configured host gala with a fixed address of 192.168.42.88, the server would fail to start at all! It’s a basic, common-sense effect when you stop to think about it, but I’ve actually tripped up on that more than once. Save yourself!
Now, start up the DHCP service by running /etc/init.d/dhcp start
on your Debian system, or service dhcpd start
on Red Hat/Fedora machines. Once you configure your hosts to actually use DHCP instead of statically assigned addresses, restart their network services, and they should be assigned addresses from your shiny new server!
“Integrate DHCP and DNS with Dynamic DNS Updates” [Hack #21]
Assign dynamic hostnames and IP addresses, and update your DNS server to reflect changes with no administrative intervention or scripted hacks.
If any two services are begging to be integrated, it’s BIND and DHCP. Dynamically assigning IP addresses with DHCP isn’t so useful if it makes your DNS zone information obsolete! Imagine if all of your configured printers got dynamically assigned IP addresses from your DHCP server. The next time your default printer got a new IP address from the DHCP server, addressing that host by name could return an unexpected result from DNS, because they’re not in sync. Where your print job winds up could be anybody’s guess.
With older versions of the ISC DHCP server and BIND, this problem was solved in one of two ways. First, you could just tell your DHCP server to statically assign addresses to your hosts [Hack #20] . This is still a useful solution to the problem, especially if the DHCP server delivers information besides an IP address, such as which NTP servers and NIS servers to use. The second option is to grab a tool (or script one yourself) to perform DNS updates.
In more recent versions of DHCP and BIND, both services support a mechanism for performing dynamic DNS updates (defined in RFC 2136), whereby an authorized user can add and delete records from forward and reverse zone files. Recent versions of DHCP also support a more flexible mechanism for deriving a dynamic hostname from an expression, which can include data sent from the client in the DHCP request.
Add these together, and you have the ability to, for example, maintain a dynamic address pool that also assigns hostnames dynamically and then updates the DNS server to reflect the changes. The alternative to dynamic hostnames is to have the DHCP server use the hostname supplied by the client, but depending on the environment, this may not be desirable. In situations where there are frequent visitors from random places, hostname overlapping can cause DNS updates to fail. Also, it’s not always safe to assume that a client will supply a valid hostname (or any hostname, for that matter).
Let’s go over how to get DHCP and BIND to work together to perform dynamic DNS updates.
The very first step that needs to be performed is the generation of a key that the two services will use to communicate with each other. The DHCP server uses this key to sign update requests sent to the DNS server, and the DNS server uses it to verify the signed requests from the DHCP server. BIND 9 comes with a utility to generate this key, called dnssec-keygen. You need to make three decisions about how to run the key-generation command. The first is the name of the key, the second is the number of bits used in the key’s encryption, and the third is what form the name of the key will take.
Let’s have a look at a key generated to represent the host that’s allowed to perform the updates. We’ll make it 512 bytes long, and we’ll name the key using the fully qualified domain name (FQDN) of the host. Here’s the command:
#dnssec-keygen -a
HMAC-MD5
-b 512 -n HOST
apollo.linuxlaboratory.org
.
This generates a TSIG key and places it in a file in the current directory. The file is named K<keyname>+157+<uniqueid>.private. The contents of this file will be something similar to this:
Private-key-format: v1.2 Algorithm: 157 (HMAC_MD5) Key: y3v81k9O9z6c62KgPNlik8P6QZIEB3yb/Blw/ XE8QN46RLeC4XkptJiRA56roCcCEGSAdCJb5kmM2/S7MBrmRQ==
The important part here is the long value after the Key
keyword. Once you have this value copied to the proper places in your configuration files, you can get rid of the key files themselves.
The next step is to configure BIND to allow updates from the DHCP server, using the key you just generated. We do this step before configuring DHCP to avoid lots of log entries indicating failed update attempts from the DHCP server during the lag time between completing the configuration of both services.
The BIND server’s named.conf file will need to have its zone blocks altered to contain an update-policy
block, which lets the server know which keys can update what records in which zones. First, we need to tell the server about all the keys we want it to know about. In our simple setup we only have one, but some environments may have one key for each host that might be allowed to alter its own records. Here’s a simple block that we can add near the top of the named.conf file to inform the server of our key:
key apollo.linuxlaboratory.org. { algorithm hmac-md5; secret "y3v81k9O9z6c62KgPNlik8P6QZIEB3yb/Blw/ XE8QN46RLeC4XkptJiRA56roCcCEGSAdCJb5kmM2/S7MBrmRQ=="; };
Next, we need to reference this key in our update-policy
substatements in each zone for which the key is valid. Here’s a typical zone that has been altered to accept updates using this key:
zone "linuxlaboratory.org" in { type master; file "db.linuxlaboratory.org"; update-policy { grant apollo.linuxlaboratory.org. subdomain linuxlaboratory.org. ANY; }; };
Here, our update policy says to allow updates signed with the key apollo.linuxlaboratory.org
., as long as the update is affecting an entry that is a subdomain of linuxlaboratory.org
.. Note that the subdomain
keyword includes the hostname. Also, we allow this key to update any record type, by including the keyword ANY
on the end. This doesn’t really mean literally any record type, though: it’ll never update, for example, your SOA records! If you want to be explicit, you can list the record types (for example, A PTR
would allow updates only to those record types).
For completeness, here’s the reverse zone block, altered with a similar update-policy
statement:
zone "42.168.192.in-addr.arpa" in { type master; file "db.192.168.42"; update-policy { grant apollo.linuxlaboratory.org. subdomain 42.168.192.in-addr.arpa ANY; }; };
Both zones allow updates to any record type of any host in the zone. This effectively makes our DHCP server the “sole master” host for performing updates.
Now let’s move on to configuring our DHCP server. In our example environment, we have a lot of hosts grabbing static IP addresses from our DHCP server. We’ll also set aside a range to be assigned to visitors, who will also be assigned dynamic hostnames. This information will be sent to the DNS server, and the requests will be signed with the same key we used in the BIND configuration.
To get the configuration right, we’ll need to add a few extra settings to the global section of the file to tell the server to do dynamic updates. We’ll then define the key to use a block very similar to the one we put in our named.conf file. Here’s the first part of our newly updated dhcpd.conf file:
ddns-update-style interim; deny client-updates; authoritative; option domain-name "linuxlaboratory.org"; option domain-name-servers 192.168.42.3; option subnet-mask 255.255.255.0; default-lease-time 600; max-lease-time 7200; key apollo.protocolostomy.pvt. { algorithm hmac-md5; secret "y3v81k9O9z6c62KgPNlik8P6QZIEB3yb/Blw/ XE8QN46RLeC4XkptJiRA56roCcCEGSAdCJb5kmM2/S7MBrmRQ=="; }
The first two settings relate directly to our goal. ddns-update-style
is set to the only value that allows us to perform DNS updates in newer versions of BIND. There used to be an ad-hoc
value that was valid here, which represented a different mechanism for performing updates, but in newer versions this value is ignored and will not work. The other valid value here is none
, which is used to explicitly state that the server will not perform updates. You must specify a value for the ddns-update-style
setting on Red Hat–based distributions.
The next setting (deny client-updates
;) tells the server to deny any requests that clients may send to update their own information. We’ve set this explicitly because we’ll be assigning dynamic hostnames. If we do not set this, the server will try to use the hostname supplied by the client, which can cause problems in some environments.
The next new part of this file is the block that defines the key to use to sign updates before shipping them over to the DNS server. It is almost identical to the DNS server configuration file, and it performs the exact same function.
Once these settings are in place, the next thing to do is define which zones our DHCP server will attempt to update, on which servers, and using which keys. Here are the zone blocks in our dhcpd.conf file that we’ll need to get things working:
zone linuxlaboratory.org. { primary 127.0.0.1; key apollo.linuxlaboratory.org.; } zone 42.168.192.in-addr.arpa. { primary 127.0.0.1; key apollo.linuxlaboratory.org.; }
These, of course, must be valid zones on the DNS server listed as primary
in each block. In our case, the DNS server is on the local host, so the updates are performed over the local loopback interface and are signed with the key we created earlier.
The last step is to set up dynamic hostnames for visitors, who will get IP addresses from a predefined range. Here’s a configuration block to take care of that:
subnet 192.168.42.0 netmask 255.255.255.0 { range 192.168.42.85 192.168.42.99; option broadcast-address 192.168.42.255; option routers 192.168.42.1; ddns-hostname = concat ("dhcp-", binary-to-ascii (10, 8, "-", leased address)); }
Visitors on our subnet are assigned addresses between node numbers 85 and 99, inclusive. The hostname the DHCP server will send to the DNS server is defined using the ddns-hostname
option. The value that results from the expression, for the host that is leased the address 192.168.42.99, will be “dhcp-192-168-42-99.linuxlaboratory.org”. The first argument to concat
is a static string. The second is the binary-to-ascii
function. The arguments to that function, in order, are the base to use (10 is simple, familiar, decimal numbers), the width of each value (8 bits), the separator to place after each 8-bit value (a dash), and the value to act upon, which in this case is a variable defined by the server. There are many wild schemes for assigning host-names, but this one has served me well and is very simple.
Restart the named server, and then restart the dhcp server. Both should start without error—if you run into one, it’s likely to be a forgotten comma or a misplaced curly brace or parenthesis. If they both start without errors you’ll at least know that your configuration is syntactically correct, so let’s move on to some other things you might see in the logs.
One of the most common issues revolves around the key and how it is generated and used. You might see messages like these:
Sep 3 13:06:11 apollo dhcpd: DHCPDISCOVER from 00:e0:b8:5c:46:c6 via eth0 Sep 3 13:06:12 apollo dhcpd: DHCPOFFER on 192.168.42.99 to 00:e0:b8:5c:46: c6 (moocow) via eth0 Sep 3 13:06:12 apollo named[13005]: client 127.0.0.1#32880: request has invalid signature: TSIG DDNS_UPD: tsig verify failure (BADKEY) Sep 3 13:06:12 apollo dhcpd: Unable to add forward map from moocow. linuxlaboratory.org to 192.168.42.99: bad DNS key
More than one issue can cause these messages. For example, you might simply have mistyped the key. Make sure you have quoted strings where you need them, both in the key’s value and in the name of the key. Use the examples above to guide you, as they’re taken from a known working configuration. If that doesn’t work, consult the manpages for the configuration files themselves to make sure you got it right.
Another reason you might get these messages is because either you used an invalid name when generating the key, or you generated the wrong key type. For example, if you ran the dnssec-keygen
command with -n USER
and then named the key after the host allowed to perform the update, the key won’t work to validate either a user or a host. You’ll also be in hot water if you generated the key with -n HOST
but didn’t name the key after the host. Generating the key using the method we used in this example will get you rolling in no time.
Most other issues are caused by pretty blatant configuration typos or permissions issues. For example, when BIND accepts an update from the DHCP server, it doesn’t rewrite its zone files immediately. It generally updates them once an hour, and in the interim, it keeps the data in a journal file. If the journal file doesn’t exist, the user that named is running as needs to have permission to write to the directory where the journal files will live.
When all is well, the logs generated by a successful setup will look similar to this:
Sep 3 15:07:55 apollo dhcpd: DHCPDISCOVER from 00:0c:f1:d6:3f:32 via eth0 Sep 3 15:07:55 apollo dhcpd: DHCPOFFER on 192.168.42.98 to 00:0c:f1:d6:3f: 32 (livid) via eth0 Sep 3 15:07:55 apollo named[14931]: client 127.0.0.1#32907: updating zone 'linuxlaboratory.org/IN': adding an RR at 'dhcp-192-168-42-98. linuxlaboratory.org' A Sep 3 15:07:55 apollo named[14931]: client 127.0.0.1#32907: updating zone 'linuxlaboratory.org/IN': adding an RR at 'dhcp-192-168-42-98. linuxlaboratory.org' TXT Sep 3 15:07:55 apollo named[14931]: zone linuxlaboratory.org/IN: sending notifies (serial 8) Sep 3 15:07:55 apollo dhcpd: Added new forward map from dhcp-192-168-42-98. linuxlaboratory.org to 192.168.42.98 Sep 3 15:07:55 apollo named[14931]: client 127.0.0.1#32907: updating zone '42.168.192.in-addr.arpa/IN': deleting rrset at '98.42.168.192.in-addr.arpa' PTR Sep 3 15:07:55 apollo named[14931]: client 127.0.0.1#32907: updating zone '42.168.192.in-addr.arpa/IN': adding an RR at '98.42.168.192.in-addr.arpa' PTR Sep 3 15:07:55 apollo named[14931]: zone 42.168.192.in-addr.arpa/IN: sending notifies (serial 6) Sep 3 15:07:55 apollo named[14931]: client 192.168.42.3#32903: received notify for zone 'linuxlaboratory.org' Sep 3 15:07:55 apollo dhcpd: added reverse map from 98.42.168.192.in-addr. arpa. to dhcp-192-168-42-98.linuxlaboratory.org Sep 3 15:07:55 apollo dhcpd: DHCPREQUEST for 192.168.42.98 (192.168.42.3) from 00:0c:f1:d6:3f:32 (livid) via eth0 Sep 3 15:07:55 apollo dhcpd: DHCPACK on 192.168.42.98 to 00:0c:f1:d6:3f:32 (livid) via eth0
“Quick and Easy DHCP Setup” [Hack #20]
A simple NTP service that saves you hours of headaches can be set up in minutes.
The Network Time Protocol (NTP) is a service that seeks to synchronize the clocks of all its clients. An NTP daemon runs on a server, synchronizes its local system’s clock with a public NTP server, and then serves as a time host so clients on the local network, including desktop PCs, can synchronize their clocks.
The number one reason to do this applies to environments of all sizes, and that reason is to enable you to easily correlate data in the logfiles on your systems. (It’s also a convenient way to ensure that your coworkers meet you for lunch at the right time.) Even if you have centralized logging, there may be applications that only log locally, and any localized audit daemons, sar configurations, and login records kept in utmp and wtmp data files need to be kept in sync so that your troubleshooting or postmortem investigations don’t begin with a list of hosts and their time offsets from the log server. You should also know that a central log host running Linux and running the syslogd daemon records a timestamp in the logfiles that corresponds to the time that the message was received, according to its local time, so that it can at least keep some semblance of order in its own logs.
Further encouragement to use an NTP service for your hosts will come from anyone who has ever had to maintain NFS servers and clients in an environment that does not synchronize time across the hosts. This can cause major issues with NFS, resulting in inexplicable “stale file handle” messages and mysterious make
command errors stating that some required file has a “modification time in the future.”
Now that you’re convinced that having an NTP service is the right thing to do, let’s move on to configuring a simple NTP server, and configuring your clients to use it.
First, you should make sure that you have the ntpd package installed. SUSE, Fedora, Red Hat, Mandrake, Debian, and all Debian variants that I’ve seen (including Ubuntu and Linspire) include ntpd. Red Hat–based systems even include it for minimal server installations. The configuration file for the server daemon is /etc/ntpd.conf, so let’s start there by having a look at a barebones configuration:
## Default rules for all connections restrict default nomodify notrap noquery ## Allow full access to the local host restrict 127.0.0.1 ## Our client subnet restrict 192.168.42.0 mask 255.255.255.0 nomodify notrap # Our timeservers server ntp.cs.princeton.edu server clock.linuxshell.net server ntp0.cornell.edu
OK, this is enough to get us started. The first line is a list of configuration keywords. The first two, restrict
and default
, define this line as the default access rule for all connections. The next three disallow remote hosts to modify the local server’s configuration (nomodify
), deny special ntpdq trap messages (notrap
), and deny ntpdq/ntpdc queries to this server (noquery
). Note that the noquery
option is specific to queries regarding the status of the server itself, not the time: time queries are unaffected by that option.
All those restrictions may seem to make setting up the server pretty useless, but just remember it’s a default rule that will be overridden by rules further down in the file.
The next line of the file, restrict 127.0.0.1
, allows full access to the local host—and no, that’s not a typo on my part. If you’ve never studied the ntpd.conf file, it looks weird to see a line starting with restrict
that ultimately gives full access to the target of the rule. However, the way that the server reads the file is that it matches up incoming connections with all of the restrict
statements, in the order they appear in the file. The keyword restrict
is followed by a hostname, IP address, or the keyword default
, followed by whatever restrictive flags you deem necessary. The absence of these flags means there are no restrictions, which is why the above line gives full access to the local host!
The next uncommented line gives access to our local subnet (192.168.42.0), so that users on this subnet can use this machine as their time server but cannot perform actions of any kind on the service itself.
The next three (uncommented) lines in the file are the servers that the local NTP server will trust for purposes of synchronizing the local clock. There are thousands of publicly available time servers worldwide, so consult one of the many lists online, find a few that are geographically close to you, and use them. You should be able to find a list by browsing the ISC web site, which maintains information about time server lists at http://ntp.isc.org/bin/view/Servers/WebHome. Do not put IP addresses in for the servers! As sites evolve, inevitably they incur some alterations in how networking works, how IP blocks are subnetted, and the like. An IP address change at Cornell University isn’t something you should be concerned with, and you won’t have to be if you use hostnames instead of IP addresses, because sites generally take care to make sure that packets bound for ntp0.cornell.edu get there regardless of the IP address of that server at the time.
It happens. Maybe you’ve lost connectivity to the outside world. Maybe you picked three NTP servers that are at the same site (a bad idea) and they’re all down. Regardless, you have clients to serve, and you need to tell them something. Enter the magical “fudge” statement:
server 127.127.1.0 fudge 127.127.1.0 stratum 10
Here, we enter the IP address of the local system’s clock in the server
line and then “fudge” its priority to stratum 10
. All time servers are automatically assigned strata values based on their distance from the time source. Many of the public time servers are stratum 2 or 3 time servers. That means that the only way our local NTP daemon is ever going to use a stratum 10 time server is if it’s the only one available. Most Linux distributions, and many other Unix variants, supply you with a default ntp.conf file that has this bit of configuration wisdom already uncommented. It’s safe to leave it uncommented, and doing so will mean that you don’t have to worry about NTP if you lose outside connectivity or if you don’t catch a hiccup in time server availability right away.
Very detailed NTP documentation by the creator of NTP, David Mills: http://www.eecis.udel.edu/~mills/ntp/html/index.html
Setting up a central X Window System font server simplifies font distribution and reduces clutter and resource use on X-based desktop systems.
The X Window System is the underpinning of most of the graphical desktops and window managers used on Linux and Unix systems today. While alternatives are under development and many people complain about the CPU impact of the X Window System’s constant polling for keyboard and mouse events, it’s hard to argue with success—the X Window System already works and is therefore used almost everywhere. Also, the demands it puts on modern systems with beefy processors are much less significant than they were on old workstations or systems running at 300 MHz. As an inherently network-aware client/server graphics system, X has a lot going for it in terms of usability and portability, as well as ubiquity, since it’s available and supported on almost every system with graphical capabilities. Still, there are some aspects of X that can be optimized—specifically, its font handling. This hack explores how you can set up a central font server to offload local font requirements to a central resource, saving CPU cycles, disk space on your desktop systems, and administrative headaches by ensuring that the same fonts are deployed on all desktop systems that might need them.
Obtaining and managing the fonts used by graphical applications has always been a problem, regardless of the type of system that you’re using, and it certainly isn’t limited to the X Window System. I can (painfully) remember choking older Windows and Mac OS boxes by installing too many fonts. Somewhat worse than the problem of loading and supporting zillions of fonts were aesthetic problems caused by people’s zealous overuse of them. I can remember getting resumes from prospective employees that looked like they’d been blasted with a shotgun loaded with different fonts and wingdings. Such is life—if you build it, they will abuse it. However, sysadmins are the wrong people to enforce aesthetics. Our job is typically to provide users with the resources they think they need and to do so in a manageable, easily administered fashion.
Today’s X Window System deployments on Linux boxes typically come from either Xfree86.org or X.org. The latter is more prevalent and is probably “the X Window System of the future” (which some may view as an oxymoron, but that’s another topic). Both of these X Window System implementations come with a variety of fonts located in subdirectories of /usr/X11R6/lib/X11/fonts. Each of these subdirectories can contain many different font families as well as individual fonts. The default X.org configuration on the SUSE system where I’m writing this provides 30 subdirectories of fonts, and running the fc-list
command shows that there are 652 separate fonts available on the system. In comparison, my Fedora Core 4 systems (where I’ve installed everything, since disk space is cheaper than my time) have 185 fonts installed. The SUSE boxes devote around 100 MB of disk space to font storage, while the FC4 system uses a mere 50 MB. These values would be much higher if I’d installed all of the fonts that are available for different languages. The number reported by the fc-list
command is also independent of any fonts that individual users may have installed locally in their ~/.fonts directories. Yikes!
Discrepancies between the number of fonts delivered with various Linux distributions and X Window System implementations make it desirable to share fonts between systems. Disk space is as cheap as dirt nowadays (certainly cheaper than potting soil), but making the same huge collections of wonderful fonts available to everyone’s X Window System applications is certainly logistically attractive. In addition to the default sets of fonts provided with Linux distributions, some Linux applications that may not be part of default system installs come with their own sets of fonts.
Luckily, I’m not the first person to have wished for a centralized mechanism for delivering fonts to X Window Systems across the college or enterprise. For quite a while, most X Window System implementations have come with a font server known as xfs (X Font Server, not to be confused with the XFS journaling filesystem). A previous incarnation of a font server, fs, was provided with older Linux distributions, but this has since been supplanted by xfs. Most desktop Linux distributions use xfs to deliver fonts to the local system, but with a few changes to the xfs configuration file and a bit of organization, you can easily configure one or two centralized font servers to handle your organization’s font requirements and make as many fonts as possible available to all of your X Window System desktops, window managers, and applications.
Setting up an X font server to serve fonts to your other systems is quite simple. As most modern X Window System implementations use a font server to deliver fonts to the local system, the most important step in the reconfiguration process is to open up the X font server to external TCP requests.
The configuration file that controls the behavior of the xfs font server is the file /etc/X11/fs/config. (Although the font server executable has a new name, they kept its old name in the path for consistency’s sake.) We’ll want to modify a few things in this file, but the critical one for turning a specific X font server instance into a centralized resource is to comment out the following line by using a text editor to put a hash mark at the beginning of the line:
#no-listen = tcp
By removing the no-listen
directive, this tells the X font server to begin listening to incoming TCP requests from other hosts.
Setting up more than one font server is a good idea if you’re going to be configuring your desktop systems to use centralized font resources. To identify other font servers, add an entry to the xfs configuration file that gives the comma-separated names or IP addresses of the other font servers on your network and the ports on which they are servicing requests. As an example:
alternate-servers = font2.vonhagen.org:7100,font3.vonhagen.org:7100
This entry tells the font server that it can redirect requests to the alternate font servers font2.vonhagen.org and font3.vonhagen.org on port 7100 if it has too many connections to handle itself. The standard port on which X font servers run, which you should probably use, is 7100. You can use a different port if you’d like, as long as you’re consistent both on the font server and on any clients that want to connect to it.
Next, you’ll want to set the port
keyword in the xfs configuration file to the integer value of the port on which the X font server will be listening for incoming requests. Again, port 7100 is the standard and should thus be used unless you have some reason to use another port.
On some Gentoo Linux distributions, the X font server port is set by the XFS_PORT
directive in the /etc/conf.d/xfs configuration file. If you are using Gentoo and your font server starts but you can’t contact it, check this file to make sure the font server is actually following the directives that you specified in its configuration file.
Next, determine the appropriate limits for the number of clients that can connect to the font server and how the font server should behave when that limit is reached. This is done by a combination of the client-limit
and clone-self
settings in the xfs configuration file. The client-limit
setting requires an integer value that determines the maximum number of clients that a specific font server will support before it refuses service to incoming requests. The clone-self
setting requires a Boolean value and determines how the font server behaves when this limit is reached. If clone-self
is on (true), the font server will start a new instance of itself when it reaches the maximum number of clients specified. If clone-self
is off (false
), the font server will attempt to contact any other servers identified in the alternate-servers
entry, in order, until one can be contacted successfully. In environments with multiple centralized font servers that service large numbers of desktops, I’d suggest always having clone-self
set to false
, and starting out with a client-limit
of 100
. Once you see how well this performs, you can raise or lower this limit to best balance response time from the font server (affected by both system load and network bandwidth) with reasonable utilization of all of your font servers.
As the final step in creating your X font server’s configuration file, you’ll need to add each directory that contains X fonts to the comma-separated value for the catalogue
statement. The next section explains how to copy font files from remote systems to the X font server system and create appropriate entries for them in the font server configuration file.
The next step in configuring your font server is to actually populate it with all of the fonts that you want it to deliver to your X Window System clients. The easiest way to do this is to examine the X Window System configuration files on each of your types of systems to see where they are currently getting fonts. This is specified in one or more FontPath
statements in the Files
section of the X Window System configuration file, which is either /etc/X11/xorg.conf (for X Window servers from X.org) or /etc/X11/XF86Config-4 or /etc/X11/XF86Config (for X Window servers from XFree86.org). If the Files
section contains a single, uncommented statement such as the following, that system is using itself as a local font server:
FontPath "unix/:7100"
For each FontPath
entry that points to an actual directory on the system you’re examining, first check if the same directory (with the same contents) exists on the system where you’ll be running your enterprise-wide X font server. If not, you’ll need to copy the contents of that directory to the X font server system, creating the directory if necessary. Once any necessary directories have been cloned to the X font server system, make sure that those same directories are being identified in the catalogue
statement in the X font server’s configuration file. For example, the following are some sample statements from an X font server configuration file that refers to a local font directory:
FontPath "/usr/X11R6/lib/X11/fonts/misc:unscaled" FontPath "/usr/X11R6/lib/X11/fonts/local" FontPath "/usr/X11R6/lib/X11/fonts/75dpi:unscaled" FontPath "/usr/X11R6/lib/X11/fonts/misc/sgi:unscaled"
After copying font directories to the machine that will be running the X font server, you will then have to update the X font server’s configuration file, /etc/ X11/fs/config, to have equivalent statements. Each FontPath
statement in the X server’s configuration file translates into one of the comma-separated values associated with the catalogue
keyword in the X font server’s configuration file. Thus, an equivalent statement to the previous example would be the following:
catalogue = /usr/X11R6/lib/X11/fonts/misc:unscaled, /usr/X11R6/lib/X11/fonts/75dpi:unscaled, /usr/X11R6/lib/X11/fonts/100dpi:unscaled, /usr/X11R6/lib/X11/fonts/misc/sgi:unscaled
If you copy fonts into an existing font directory on the machine that will be running the X font server, you should su
to root or use the sudo
command to re-run the mkfontdir
command so that all the fonts in that directory can be identified and delivered by the X font server.
Almost there! Before restarting the X font server to pick up the new settings and start offering fonts to any X clients that happen by, check the xfs startup script located in /etc/init.d/xfs to make sure that it doesn’t explicitly specify a different port than the one on which your font server expects to listen. For example, suppose that your startup script contained a statement like the following:
daemon --check xfs xfs -port -1 -daemon -droppriv -user xfs
You would want to modify this statement to the following:
daemon --check xfs xfs -port 7100 -daemon -droppriv -user xfs
Some startup scripts use a variable to hold the port number. If this is the case in your font server startup scripts, make sure that the variable identifies port 7100 as the port on which to run the server.
You’re now officially ready to go! To restart (or start) your X font server, simply execute the following command:
# /etc/init.d/xfs restart
If the font server isn’t already running, the stop portion of the restart will fail, but the start portion will start your X font server with all your new options.
Make sure you add the X font server startup fairly early on in your various runlevels, especially if you start your machine in graphical mode. If the X font server isn’t available when you try to start the X Window System itself, and no local fonts are available, X will fail to start.
While there’s a bit of work involved in setting up your X font server and making sure it offers all the fonts your clients will need, switching a desktop system to use a remote X font server is easy. As mentioned previously, many modern desktop Linux distributions already use a local font server (i.e., a font server that is running on the same host as the X server) to deliver fonts. Switching these systems to use a remote X font server is extremely easy.
The key to where your X server gets its fonts is the Files
section of its configuration file. As stated previously, if the Files
section contains a single, uncommented statement such as the following, that system is using itself as a local font server:
FontPath "unix/:7100"
To switch this system to using the remote font server, change this line to something like the following:
FontPath "tcp/fontserver1.vonhagen.org
:7100"
You should then restart the X server on your desktop system to ensure that it can contact the X font server and retrieve the fonts that it needs. If the system cannot contact the font server, starting X will fail, and you should follow some of the tips in the troubleshooting section of this hack. Otherwise, you’re done!
Though I’m a fan of centralizing X resources such as fonts to make everyone’s lives easier, I use a somewhat paranoid X server configuration file that provides some fallback in case a font server or the network goes down. For example, the FontPath
entries in the xorg.conf files for machines on my home office network are the following:
FontPath "tcp/fontserver1.vonhagen.org:7100" FontPath "tcp/fontserver2.vonhagen.org:7100" FontPath "/usr/X11R6/lib/X11/fonts/75dpi:unscaled" FontPath "/usr/X11R6/lib/X11/fonts/misc:unscaled" FontPath "/usr/X11R6/lib/X11/fonts/local"
This tells my X servers to try two local font servers first and then fall back to a minimized collection of local fonts if the font servers don’t work for some reason. The font servers are just CNAMEs in my DNS server, so I can easily move them to different hosts as my computing environment evolves. The fallback entries cover those rare cases when I just want to start a single machine.
If an X server can’t contact your font server and that is the only font resource and you haven’t provided any local fallback fonts, the X server will not start and will terminate with a message about not being able to contact the font server. Luckily, both Linux and the X Window System include some helpful commands to enable you to diagnose the problem.
First, on the font server, make sure that the font server is actually running by using the ps
command, as in the following example:
$ ps -ef | grep xfs
root 13841 31053 0 04:39 pts/13 00:00:00 xfs
wvh 13848 31053 0 04:39 pts/13 00:00:00 grep -i xfs
Next, check that you can contact it successfully by retrieving a list of the fonts that it provides. You can do this using the fslsfonts
command, as in the following example:
$fslsfonts -server
fontserver1
:7100
This should display a long list of available fonts. If it doesn’t, make sure that the font server is actually listening on the correct port, using a command like the following:
$ netstat -an | grep 7100
tcp 0 0 0.0.0.0:7100 0.0.0.0:* LISTEN
tcp 0 0 :::7100 :::* LISTEN
unix 2 [ ACC ] STREAM LISTENING 862009 /tmp/.font-unix/
If you don’t see this information and you can’t contact the X font server using fslsfonts
, make sure that you commented out the no-listen = tcp
entry in your font server’s configuration file.
If you see messages like the following when you start your X font server, some of the directories specified in its configuration file either don’t exist or don’t contain valid fonts:
xfs notice: ignoring font path element /usr/X11R6/lib/X11/fonts/Speedo (unreadable) xfs notice: ignoring font path element /usr/X11R6/lib/X11/fonts/CID (unreadable) xfs notice: ignoring font path element /usr/X11R6/lib/X11/fonts/local (unreadable)
These messages are nonfatal, but you should clean up your font server’s configuration file so that no other sysadmin is confused about whether these directories were supposed to contain fonts that have somehow gotten “lost.”
Finally, double-check that you have the right font server settings in your X server’s startup file. You can use the fallback approach suggested in the previous section to start the X font server using a small collection of local fonts until you resolve your connectivity problems. While you’re working on them, you can use the fslsfonts
and xset fp
(set font path for the X Window System) commands to, respectively, test connectivity to the font server and add it to your current X session for testing purposes. The xset fp
command enables you to add a font server to the list of font sources that X applications search (known as a font path), using a command like the following:
$xset +fp tcp
/fontserver1
:7100
You may need to cause the X server to re-probe its font sources by using the xset fp rehash
command. While testing, you can also remove elements from your X font path using a command like the following:
$xset -fp tcp
/fontserver1
:7100
You can determine the current settings for your X font path (along with other settings) by executing the xset -q
command, which provides a variety of information about your working X Window System environment.
The X Window System is a great thing for Linux and Unix users, but the number of available (or required) fonts can quickly escalate, especially in internationalized (I18N) environments. Some X Window System applications, such as Wolfram Research’s Mathematica, also take advantage of many custom fonts in order to display results as nicely as possible. (Wolfram’s docs even identify a font server that they export over the Internet for this purpose.)
Centralizing resources such as fonts that are used by many of the machines in your computing environment can save local disk space and, more importantly, provide a single location where you can easily install custom fonts that multiple users may require. Be careful, though—central resources simplify administration, but they can also provide single points of failure unless you architect your installation correctly.
The disk space required to install most fonts locally is no big deal (or expense) nowadays. However, centralizing custom fonts is always a good idea. Installing custom fonts locally isn’t really a problem until you upgrade or replace the machine on which they live, at which point you may forget that the machine had custom fonts installed. Once bitten, twice shy! An X font server is easy preventative medicine for this sort of problem.
Let printers announce themselves and create a flexible, modern printing environment by setting up CUPS.
Today’s printers are typically high-quality laser or inkjet printers, often capable of color printing and near-photographic quality. The original Unix printing system, known as lpd (Line Printer Daemon) was designed to queue and print jobs that were intended for huge, text-only line printers. As more sophisticated printers were developed that were capable of higher-quality printouts (such as the original x9700, Canon-CX, and Imagen-300 laser printers), the original lpd print system continued to be used, but it required that the jobs you were printing be preprocessed so that they contained the special commands the printer used internally to produce higher-quality printouts. This quickly became tedious, because it meant that users had to know which printers they wanted to print to and required use of the appropriate preformatting commands. Eventually, the lpd system was updated and a similar printing system known as lp was developed. lp encapsulated the knowledge about the formats required by specific printers, implementing the necessary preformatting commands into filters (also known as print drivers) that automatically formatted files as required by the target printers.
The evolution of multiple printing systems for Unix systems was not without pitfalls: it led to incompatibilities between the different print systems, required recompilation of the filters for specific printers for multiple Unix systems (if you could get the source code at all), and so on. Eventually, a company known as Easy Software Products began developing a more generalized printing system for Unix, Linux, and other Unix-like systems, called the Common Unix Printing System (CUPS). The original version of CUPS used the standard networked LDP protocol, but it quickly switched to using a new standard, the Internet Printing Protocol (IPP), which non-Unix/Linux systems such as Windows can use to print to CUPS printers. Easy Software Products also had the foresight to make the CUPS source code freely available under the GPL so that it could be compiled for multiple operating systems and thus become a true, cross-system standard popularized by zillions of users and sysadmins. This strategy has worked—today, CUPS is used by every major Linux distribution and most other Unix-like systems.
Almost every Linux system provides its own administrative tool for print system and printer configuration: SUSE provides YaST; Red Hat and Fedora Core distributions use printconf-gui; and so on. Printer configuration would therefore still be a sysadmin nightmare if not for the fact that the CUPS print daemon provides a built-in administrative tool that is easily accessed through any web browser via port 631. This provides a standard interface for CUPS configuration (though you’re still welcome to use your Linux distribution’s administrative printer configuration tools, if you insist). This hack focuses on the standard CUPS interface and web-based configuration.
To define a new printer on any Linux system using the CUPS administrative interface, you must first make sure that the CUPS daemon, cupsd, is running on your system. You can do this using the ps
command, as shown in the following example:
$ ps alxww | grep cupsd
5 4 6923 1 16 0 24540 1452 - Ss ? 0:00 /usr/sbin/cupsd
0 1000 13304 31053 17 0 536 112 - R+ pts/13 0:00 grep -i cupsd
If it isn’t shown in the process listing, you can start it as the root user or via sudo, as in the following example:
# /etc/init.d/cups start
You should see an OK message once the system starts the CUPS daemon. Next, open your favorite web browser and connect to the network address http://127.0.0.1:631. The odd port number comes from its roots as an IPP print server (the default port for IPP is 631). The screen shown in Figure 3-1 will display.
When you see this screen, click the Do Administration Tasks link. An authentication dialog will display, into which you enter the name and password of a user who is authorized to do printer configuration on your system.
The users who can administer printers and the print subsystem differ across multiple Linux distributions. On SUSE Linux systems, you must add authorized users to the CUPS authentication file using the lppasswd
command (for example, lppasswd–a wvh
would add the user wvh and prompt you twice for a password for printer administration by that user). On Red Hat, Fedora Core, and many other Linux distributions, you can simply enter the root user’s login and password.
Once you successfully enter an authorized user’s name and password, the screen shown in Figure 3-2 will display.
Click Add Printer to display the screen shown in Figure 3-3, where you can begin configuring your printer. (You can also get to this screen by selecting the Printers item from any CUPS page header and clicking the Add Printer button, but I think of this as an administrative action and therefore usually get there from the Admin page.) This hack focuses on configuring a local (physically attached) printer. “Configure Linux Connections to Remote CUPS Printers” [Hack #25] provides information on configuring a remote printer is provided in.
Enter a memorable short name for the printer in the Name field (most commonly without spaces), enter a summary of the printer’s location in the Location field, and enter a short description of the printer in the Description field. The latter two are simply text strings, but putting meaningful values in these fields will help you remember which printer is which if your print server supports multiple printers. Click Continue to proceed. The screen shown in Figure 3-4 will display.
Select the device to which your printer is attached from the drop-down list shown in Figure 3-4. As you can see, some Linux distributions auto-identify the printers attached to various ports when they perform hardware detection (this example screen was captured on a SUSE Linux system). After selecting the interface to which your printer is attached, click Continue. The screen shown in Figure 3-5 will display.
Select the manufacturer of your printer from the drop-down list in Figure 3-5. If the manufacturer of your printer isn’t explicitly listed, your printer probably emulates a printer from some other manufacturer. (Printers that emulate various Hewlett-Packard printers are quite common. Any printer that supports PCL—HP’s Printer Control Language—can emulate some sort of HP printer.)
You’ll note that many print drivers provide two printing options: gimp-print and foomatic. gimp-print is a print plug-in for the GNU Image Manipulation Program (GIMP) graphics package that includes many custom print drivers, while foomatic is a database-driven interface to another set of print drivers. I generally select whichever is marked as Recommended. If neither is recommended, it’s usually best to start with gimp-print drivers, because gimp-print can also access foomatic drivers, but foomatic can’t access the gimp-print drivers. Most Linux distributions preinstall these packages when you install CUPS, but you may have to install them separately on distributions whose goal is minimizing disk usage.
Click Continue to proceed. A screen like the one shown in Figure 3-6 will display, listing all of the printers that are available from the selected manufacturer.
Select your printer (or an equivalent) from this list. It’s important to select your printer exactly if possible, to best take advantage of the printer’s capabilities. Click Continue to proceed. You’ll see a summary screen telling you that the printer has been set up, which includes creating the right print queues and configuration entries that are used internally by CUPS.
Once you’ve set up a new printer, the first thing you’ll want to do is test printing to it, not just to ensure that it is correctly configured in terms of ports and drivers, but also to check the default quality level for the printer. To do this, click the Printers entry in the heading of any CUPS administrative web page. A screen like the one shown in Figure 3-7 will display.
Click the Print Test Page button. You should see your printer’s activity light come on, and the printer should begin to print a CUPS test page. If the activity light doesn’t come on, click the Jobs entry in the web page to display a page showing the status of the test print job. If this page shows that the job has completed, your printer is not configured correctly. The most common problems are that the printer isn’t connected to the port that you selected in Figure 3-4, or that you’ve selected the wrong print driver. You can review and modify your current settings by clicking the Modify Printer button on the Printers page, which walks you through the steps described in the previous section using your current settings as defaults.
After you’ve successfully configured a printer and printed a test page, you may want to fine-tune your printer’s print capabilities. To do this, click the Printers entry in the heading of any CUPS administrative web page, and click the Configure button. A screen like the one shown in Figure 3-8 will display. The contents of this page will depend on the capabilities of your printer and the driver that you selected, but they will enable you to do things like fine-tune color settings (in the Adjustment section), select a higher default printing resolution (using the General section’s Printout Mode setting), and so on.
Depending how CUPS is preconfigured on your Linux distribution, you may need to add your remote hosts (or your entire network) to the list of acceptable locations in the CUPS daemon’s configuration file, /etc/cups/cupsd.conf. The list of valid locations for incoming print jobs is stored inside the <Location />… </Location>
stanza. On most systems, this looks like the following:
<Location /> Order Deny,Allow Deny From All Allow From 127.0.0.1 </Location>
This configuration file entry supports printing to the CUPS server from the host on which the print server is running. To change the entry so that all hosts on the local network can print, add a line so the stanza now looks like this:
<Location /> Order Deny,Allow Deny From All Allow From 127.0.0.1Allow From
192.168.6.*
</Location>
This stanza now enables printing from the local host and from all printers on the specified subnet (in this case, 192.168.6).
CUPS print servers maintain three logfiles (stored in the directory /var/log/cups) that provide some information about attempts to access or use them:
Records attempts to access the CUPS print server. Can be useful in determining why print jobs are rejected or discarded.
Records all errors encountered or produced by the CUPS print server. Can be equally useful in determining why print jobs are rejected or discarded.
Keeps track of every page printed by a specified printer, including the host from which the print job was received, the name of the printer being used, and so on.
Of these, the access_log and error_log files are the most useful for diagnostic purposes. Examining the end of these files after attempting to print but not receiving any output usually shows meaningful error messages. For example, if you forgot to update the MIME files and are trying to print to a CUPS printer from Windows, you may see messages like the following:
E [05/Sep/2005:17:55:49 -0400] get_job_attrs: job #0 doesn't exist! E [05/Sep/2005:17:55:49 -0400] print_job: Unsupported format 'application/ octet-stream'! I [05/Sep/2005:17:55:49 -0400] Hint: Do you have the raw file printing rules enabled?
It doesn’t get much more helpful than this in terms of identifying the problem and suggesting a fix.
The page_log file can be useful for cost diagnosis. A number of open source applications are available to parse and summarize the information in this file, helping you get some idea of your printing costs. Useful applications that do this sort of thing are PrintAnalyze and phpPrintAnalyzer, both of which are available from the CUPS web site at http://www.cups.org/links.php. Another useful script along the same lines is cartridge_usage.pl, a Perl script that requires that you keep a separate logfile for each new cartridge but does a great job of identifying the number of pages that each cartridge will print. This script is available at http://www.ime.usp.br/~feferraz/en/cartusage.html.
CUPS provides a central system for printing to modern printers on Linux and many other operating systems. Its combination of support for standards, consistency across platforms, and a common, web-based administrative interface makes it a powerful, usable package. As we’ll see in the next few hacks, it’s easy to configure printing to CUPS print servers from remote Linux, Windows, and Macintosh systems.
“Configure Linux Connections to Remote CUPS Printers” [Hack #25]
“Integrate Windows Printing with CUPS” [Hack #26]
“Centralize Macintosh Printing with CUPS” [Hack #27]
“Define a Secure CUPS Printer” [Hack #28]
Quickly set up connections to remote printers using the CUPS web-based interface.
It would be nice if each user had her own printer, so we could all avoid the inherent bottlenecks caused when some thoughtless user prints a 100-page manual or a bunch of high-resolution vacation photos to one of your school’s or company’s central printers. Unfortunately, the purchase and maintenance costs of high-volume printers can be quite high, so most schools and businesses concentrate resources on one or two good ones and configure all their desktop systems to send print jobs to those printers. Luckily, the web-based administrative interface provided by CUPS makes it quite simple to configure and test connections to remote CUPS printers on Linux systems. Here’s how.
The basic procedure for defining the remote printer is almost identical to that for creating the CUPS print server [Hack #24] , so I won’t insult your intelligence by duplicating screenshots and instructions here. Instead, I’ll just focus on the two screens that are different and that really matter: the Device screen, where you specify how to connect to the printer; and a new Device URL screen, where you specify the Universal Resource Locator (URL) that uniquely identifies the remote printer.
After authenticating and beginning the process of adding a printer, you’ll need to specify the protocol with which your client system will communicate with the remote printer. This is done on the Device screen, shown in Figure 3-9. Instead of selecting a physical connection, you’ll usually select the Internet Printing Protocol (IPP). IPP is a modern protocol for communicating with printers from many different types of operating systems, and it is therefore the right choice in most modern, mixed-system environments.
Once you’ve selected IPP, click Continue to proceed to another Device screen, shown in Figure 3-10. This screen enables you to specify the URL of the remote printer so that the local system knows where to find the correct printer.
As shown in Figure 3-10, the URL of remote CUPS printer is in the form ipp://address-or-name/printers/printer-name
, where address-or-name
is the IP address or name of the host to which the printer is physically attached, and printer-name
is the name of that printer on the remote host. The URL shown in this figure reflects the print server that I defined in “Create a CUPS Print Server”
[Hack #24]
, which is named epson-color200 and is running on the host 192.168.6.64.
Once you’ve specified the URL for the remote printer, proceed through the rest of the printer configuration screens [Hack #24] . You’ll probably also want to print a test page to ensure that you can connect to the remote printer and verify that you selected the correct print driver to format output for the remote printer.
Configuring print access from any Linux system to a remote CUPS printer is quite easy, as you can see from the simple case explained in this hack. If you need to restrict access to this printer, you can manually modify the CUPS configuration file (/etc/cups/cupsd.conf) on the print server, as explained in “Define a Secure CUPS Printer” [Hack #28] .
Using CUPS as the printing and queuing mechanism for your school or enterprise is the perfect solution—it gives you a powerful, consistent printing utility with a consistent administrative interface that is independent of different Linux distributions, thanks to its web-oriented focus.
“Create a CUPS Print Server” [Hack #24]
CUPS is not just a great solution for Linux and Unix printing—it can also easily handle your Windows printing needs.
As we all know, it’s important to be able to play nicely with Windows systems in today’s academic and business environments. This may be philosophically unattractive to many of us, but it’s a reality. While printing from Windows systems to Linux print servers is often done using Samba (leveraging the standard SMB/CIFS networking protocols), you may not want to set up Samba on every desktop for which you’re responsible. Luckily, Microsoft’s quest for proprietary standards hasn’t eliminated their support for remote printing using other standard protocols, such as HTTP, which CUPS is happy to support. This hack explains how to configure Windows systems to print to remote CUPS print servers using the standard HTTP protocol.
It’s really quite easy to configure a Windows 2000 or XP system to print to a remote CUPS printer. First, select the standard “Add printer” icon from the Printers folder in the Control Panel. Specify that you want to create a remote printer, and enter a URL of the following form: http://name-or-address:631/printers/printer-name (as shown in Figure 3-11, which shows the Windows 2000 printer configuration dialog).
Continuing with the example used in the previous CUPS-related hacks in this book, I’ve entered the URL http://192.168.6.245:631/printers/epsoncolor200. Figure 3-12 shows the equivalent dialog under Windows XP.
Some combinations of Windows systems and CUPS versions require that you specify a hostname rather than an IP address in a printer URL. If your remote print server has a fixed IP address, the easiest way to do this is to create an entry in the Windows hosts file that maps the IP address to a hostname. This is the file C:WINNTsystem32driversetchosts on Windows 2000 systems, and the file C:WINDOWSsystem32driversetchosts on Windows XP systems. For example, adding an entry like 192.168.6.245 printserv
to this file would enable me to specify the URL http://printserv:631/printers/epson-color200 for the remote printer.
Click Next to proceed with configuring the remote printer connection. Because you are connecting to a remote printer, you may see a dialog like the one shown in Figure 3-13. This dialog demonstrates that the Windows system is able to contact the remote print server, since the warning message displays the name of the print driver as known to the remote print server. To satisfy Windows, you can either choose an installed print driver from the subsequent dialog or locate the print driver on the Web or on the CD that accompanied your printer purchase.
Once you’ve finished configuring the printer on the Windows system, you’ll need to make a few modifications to the CUPS printer configuration files on your print server. Because the files you print are being preformatted on your Windows system, and you are using the HTTP protocol, you will need to configure the CUPS server on the Linux system to which the printer is connected. You will need to modify two configuration files to tell the CUPS server how to handle raw data files received via HTTP, configuring it to send those files directly to the specified print queue with no local formatting.
First, edit the file /etc/cups/mime.types, which defines valid Multipurpose Internet Mail Extensions (MIME) formats that are supported by the CUPS server. MIME defines a variety of formats that one might encounter on the Internet (such as in a web browser or in HTTP communications) and defines how MIME-aware applications should handle them. To enable printing via HTTP, remove the hash mark (#) at the beginning of the following line:
#application/octet-stream
Without the leading comment character (the hash mark), this entry tells the CUPS print server that raw data streams are an acceptable input format. Next, edit the file /etc/cups/mime.convs, which defines the types of conversions that the CUPS server should perform on various MIME input formats. To enable printing via HTTP, remove the hash mark at the beginning of the following line:
#application/octet-stream application/vnd.cups-raw 0
As with the change to the /etc/cups/mime.types file, removing the comment character from the beginning of this line tells the CUPS server to handle input files in application/octet-stream format by passing them to a CUPS application that simply inserts them into a print queue without doing any local formatting.
The most common cause of being unable to print to a CUPS print server is that the printer is not configured to accept print jobs from your host’s IP address. For more information about this, see the section “Enabling Remote Printing on the CUPS Server” in “Create a CUPS Print Server” [Hack #24] . If you’re sure that this is not the problem, check the CUPS logfiles. The CUPS print servers maintain three logfiles that can provide a variety of information about attempts to access or use them: access_log, error_log, and page_ log. Of these, the access_log and error_log files are the most useful for diagnostic purposes. Examining the end of these files after attempting to print but not receiving any output usually shows meaningful error messages. For example, if you forgot to update the MIME files and are trying to print to a CUPS printer from Windows, you may see messages like the following:
E [05/Sep/2005:17:55:49 -0400] get_job_attrs: job #0 doesn't exist! E [05/Sep/2005:17:55:49 -0400] print_job: Unsupported format 'application/ octet-stream'! I [05/Sep/2005:17:55:49 -0400] Hint: Do you have the raw file printing rules enabled?
These messages should help you identify the problem and suggest a fix.
“Create a CUPS Print Server” [Hack #24]
“Share Files Across Platforms Using Samba” [Hack #60]
Mac OS X makes CUPS printers readily available from Macintosh systems.
Now that the Mac OS is actually a Unix system with graphical gravy, it’s much easier to get to the underpinnings of the operating system when necessary. Also, because much of the software that actually powers Mac OS X is now familiar open source software, it’s easier than ever to reapply your existing Linux/Unix knowledge to working with Mac OS X. Integrating Mac OS X printing with a CUPS server running on a remote Linux system is one of the best examples of this, because Mac OS X actually uses CUPS as the core of its printing subsystem. This hack explains how to use the familiar CUPS web interface to quickly and easily set up your Mac OS X systems to print to centralized CUPS print servers running on Linux systems. If you’re still running a version of the Mac OS earlier than Mac OS X, this hack isn’t for you unless you upgrade.
As well as supporting CUPS, Mac OS X also includes its own printer configuration tool, the Printer Setup Utility. The versions of the Printer Setup Utility provided with Mac OS X 10.4 and above can locate remote CUPS printers automatically, because CUPS supports the standard Internet Printing Protocol (IPP). However, just in case you can’t find your printer using IPP, this hack explains the details of configuring a printer using our old friend, the web-based CUPS administrative interface. The procedure discussed in this section works fine with Versions 10.2 and later of Mac OS X.
Thanks to the fact that Mac OS X uses CUPS, the basic procedure of defining a remote printer on Mac OS X is almost identical to that of configuring remote printing on Linux systems. It is therefore also almost identical to that for creating a CUPS print server [Hack #24] . As in “Configure Linux Connections to Remote CUPS Printers” [Hack #25] , I’ll focus on the two screens that are different and that really matter: the Device screen, where you specify how to connect to the printer; and a new Device URL screen, where you specify the Universal Resource Locator (URL) that uniquely identifies the remote printer.
After authenticating (using the login and password of any user with administrative privileges) and beginning the process of adding a printer, you’ll need to specify the protocol with which your OS X system will communicate with the remote printer. This is done on the Device screen, shown in Figure 3-14. Instead of selecting a physical connection, you’ll usually select the “Internet Printing Protocol (http)” entry to specify that you want to use IPP with the HTTP protocol as its transport mechanism.
Once you’ve selected IPP over HTTP, click Continue to proceed to another Device screen, shown in Figure 3-15. This screen enables you to specify the URL of the remote printer so that the local system knows where to find the correct printer.
As shown in Figure 3-15, the URL of the remote CUPS printer is in the form http://address-or-name/printers/printer-name, where address-or-name
is the IP address or name of the host to which the printer is physically attached, and printer-name
is the name of that printer on the remote host. The URL shown in this figure reflects a different print server than was used previously; it’s named silentwriter and is running on the host 192.168.6.64.
If Windows printers are available in your environment or you are running Samba on one of your systems and you prefer to print using Windows SMB protocols, you can select “Windows Printer via SAMBA” as the printing protocol that you want to use and enter a URL of the form smb://username: passwd@hostname/printers/printer-name. If you’re using a version of Mac OS X earlier than 10.4, you’ll also have to verify that /usr/libexec/cups/backend/smb is a symbolic link to /usr/bin/smbspool and, if not, create that link.
Once you’ve specified the URL for the remote printer, proceed through the rest of the printer configuration screens [Hack #24] . You’ll probably also want to print a test page to ensure that you can connect to the remote printer and verify that you selected the correct print driver to format output for the remote printer. You can do that after making sure you’ve tweaked your server’s configuration to handle HTTP print jobs correctly, as described in the next section.
Once you’ve finished configuring the printer on the Mac OS X system, you’ll need to make a few modifications to the CUPS printer configuration files on your print server. Because the files that you print are being preformatted on your OS X system and you are using the HTTP protocol, you will need to configure the CUPS server on the Linux system to which the printer is connected. You will need to modify two configuration files to tell the CUPS server how to handle raw data files received via HTTP, configuring it to simply send those files directly to the specified print queue with no local formatting.
First, edit the file /etc/cups/mime.types, which defines valid Multipurpose Internet Mail Extensions (MIME) formats that are supported by the CUPS server. MIME defines a variety of formats that one might encounter on the Internet (such as in a web browser or in HTTP communications) and defines how MIME-aware applications should handle them. To enable printing via HTTP, remove the hash mark (#) at the beginning of the following line:
#application/octet-stream
Without the leading comment character (the hash mark), this entry tells the CUPS print server that raw data streams are an acceptable input format. Next, edit the file /etc/cups/mime.convs, which defines the types of conversions that the CUPS server should perform on various MIME input formats. To enable printing via HTTP, remove the hash mark at the beginning of the following line:
#application/octet-stream application/vnd.cups-raw 0
As with the change to the /etc/cups/mime.types file, removing the comment character from the beginning of this line tells the CUPS server to handle input files in application/octet-stream format by passing them to a CUPS application that simply inserts them into a print queue without doing any local formatting.
You will need to restart the CUPS print server to make sure that it picks up these changes. The startup script for your CUPS server is called cups and is typically located in /etc/init.d. To restart the CUPS print server, execute the following command (or one appropriate for your distribution):
# /etc/init.d/cups restart
At this point, you’re ready to try a test print job. In your web browser, select the Printers button in the CUPS page header. Click the Print Test Page button and verify that a test page prints correctly on the remote printer. If so, congratulations! If not, check the Jobs status page in your web browser by clicking Jobs in the CUPS page header. If you’ve made a syntax error in your URL, you’ll see a message saying “Unable to connect to IPP host: Invalid Argument.” Correct the URL, abort the current test page, and retry printing a test page. If you don’t see any error messages but the print job claims to have completed, see the next section for some debugging tips.
Once you’ve successfully printed a page from your OS X system, you’ll notice that the printer that you defined using the CUPS web interface is also now visible in the Printer Setup Utility. Magic!
The most common cause of being unable to print to a CUPS print server is that the printer is not configured to accept print jobs from your host’s IP address [Hack #24] .
If you’re sure that this is not the problem, check the CUPS logfiles. CUPS print servers maintain three logfiles that can provide a variety of information about attempts to access or use them: access_log, error_log, and page_log.Of these, the access_log and error_log files are the most useful for diagnostic purposes. Examining the end of these files after attempting to print but not receiving any output usually shows meaningful error messages. For example, if you forgot to update the MIME files and are trying to print to a CUPS printer from Mac OS X, you may see messages like the following:
E [05/Sep/2005:17:55:49 -0400] get_job_attrs: job #0 doesn't exist! E [05/Sep/2005:17:55:49 -0400] print_job: Unsupported format 'application/ octet-stream'! I [05/Sep/2005:17:55:49 -0400] Hint: Do you have the raw file printing rules enabled?
Talk about useful error messages! Double-check the changes you made to the CUPS MIME configuration files, restart the CUPS daemon, and try printing again.
“Create a CUPS Print Server” [Hack #24]
“Configure Linux Connections to Remote CUPS Printers” [Hack #25]
Integrated support for various authentication mechanisms makes it easy to limit access to specific printers with CUPS.
The other CUPS hacks in this chapter have focused on its most excellent web-based administrative interface and how the interface simplifies and standardizes printer setup, regardless of the type of CUPS client you’re configuring. However, like most Unix/Linux programs, you can also administer the CUPS server by directly manipulating its configuration file, /etc/cups/cupsd.conf. While this may seem somewhat intimidating at first blush, the format of this file is actually quite simple and is conceptually evocative of an Apache configuration file (which we’ve all probably had to modify at one time or another). A few simple changes to this file can quickly add a new layer of security to your CUPS printing environment.
Many sysadmins are paranoid today, and for good reason. Securing your existing systems by eliminating unnecessary services is just plain smart [Hack #63] . Similarly, there may be cases where you want to restrict access to certain printers. There are many security and cost reasons for limiting access to specific printers to certain users or certain IP addresses, whether it’s because of who “owns” the printer (such as your CEO or department head) or because the printer uses platinum toner to print on gold sheets (and is therefore the wrong place for freshmen to print their CS101 homework). Here’s how to do just that with your favorite text editor (which should be emacs) and a few minutes of your spare time.
You will have to restart the CUPS server after making any changes to the CUPS configuration file, as discussed in this (or any other) hack. The startup script for your CUPS server is called cups and is typically located in /etc/init.d. To restart the CUPS print server after saving your changes to its configuration file, execute the following command (or one appropriate for your distribution):
# /etc/init.d/cups restart
Depending ‘on how CUPS is preconfigured on your Linux distribution, you may need to add your remote hosts (or your entire network) to the list of acceptable locations in the CUPS daemon’s configuration file, /etc/cups/cupsd.conf, so they can print on the printer in the first place. The list of valid locations for incoming print jobs is stored inside a <Location />…</Location>
stanza in the CUPS configuration file. The default CUPS configuration file contains a single Location
stanza, which applies to all printers that the CUPS server knows about. On most systems, this looks like the following:
<Location /> Order Deny,Allow Deny From All Allow From 127.0.0.1 </Location>
This configuration file entry supports printing to the CUPS server only from the host on which the print server is running. Many CUPS printer configuration files use the @LOCAL
macro to tell CUPS that any host that has a non-point-to-point connection to the print server can print to the printer. This generally includes hosts on the local network and typically looks like the following:
<Location />
Order Deny,Allow
Deny From All
Allow From 127.0.0.1
Allow from @LOCAL
</Location>
If you are having problems printing to a specific printer from other hosts on your network, check the /etc/cups/cupsd.conf file to ensure that the Location
stanza includes an @LOCAL
entry.
If you want to explicitly configure the CUPS server so that only hosts on a specific local network can print to the printer, remove the @LOCAL
entry and add a line for the local subnet, so that the stanza now looks something like the following:
<Location />
Order Deny,Allow
Deny From All
Allow From 127.0.0.1
Allow From 192.168.6.*
</Location>
This stanza now enables printing from the local host and from all printers on the specified subnet (in this case, 192.168.6), as well as the host to which the printer is physically connected.
The most straightforward way to create a secure printer is to put the printer in a secure location and physically restrict access to it. If you don’t have a secure location available, you can also restrict printing to a particular printer so that only hosts with specific IP addresses can print to it. To do this, you simply create a new Location
stanza in /etc/cups/cupsd.conf for that printer and use the Allow/Deny approach introduced in the previous section to identify any IP addresses that you want to be able to print to the printer. For example, a Location
stanza that restricts access to the printer silentwriter such that only the host to which the printer is actually attached and the host with the IP address 192.168.6.101 can print to it would be the following:
<Location /printers/silentwriter>
Order Deny,Allow
Deny From All
Allow From 127.0.0.1
Allow From 192.168.6.101
</Location>
Restricting access to a specific printer based on the IP address of the host that you want to allow to print to it is useful, but those pesky users often tend to move around from host to host. An alternative to restricting access by IP address is to require authentication in order to print to a specified printer. You can do this by using users’ standard Linux passwords, but I find it most useful to require a separate password for printer access. Using standard Linux passwords causes the print server to invoke the PAM modules for CUPS (defined in /etc/pam.d/cups), which often differ from Linux distribution to Linux distribution. (PAMs were discussed in “Customize Authentication with PAMs” [Hack #4] ) Also, since most people using Linux systems have Linux passwords, that approach doesn’t really limit access to any significant extent. Using a separate password for printer access is quite standard across all CUPS-oriented Linux distributions.
You can define a CUPS access password using the lppasswd
command. To add a new user to the CUPS password file (stored in /etc/cups/passwd.md5 by default), execute the following command as root or via sudo
:
#lppasswd–a
username
You’ll be prompted twice for the specified user’s password. Once a user has a CUPS password, you can add this level of authentication to a specific printer by creating a new Location
stanza for that printer (or updating an existing one), as in the following example:
<Location /printers/silentwriter>
Order Deny,Allow
Deny From All
Allow From 127.0.0.1
Allow From 192.168.6.*
AuthType Digest
</Location>
This lets anyone from the 192.168.6 subnet who has a valid CUPS password entry print to the silentwriter printer. Users will be prompted for this password whenever they try to send print jobs to the specified printer, as in the following example:
$ lpr /etc/printcap
Password for wvh on localhost?
Some applications, such as Microsoft Windows applications running under WINE, open connections to your default printer when they start up. If you start them in the background, these programs will appear to hang because they are prompting you for a printer password in the background, but you’re not seeing the prompt. If you use CUPS passwords and a specific application seems to hang, try starting it in the foreground (i.e., without a trailing ampersand) to see if it’s actually prompting you for additional information.
Beyond the simple authentication and IP address entries discussed in this hack, CUPS provides many other mechanisms for authentication, such as printer classes and alternatives to digest authentication that are outside the scope of this hack and really deserve a book of their own. As a matter of fact, there is one: Michael Sweet’s book on CUPS is complete and easy to read (and as the original author of CUPS, he should know all about it). Excellent, complete, and readable documentation is also available from the CUPS web site (http://www.cups.org/documentation.php).
CUPS: Common UNIX Printing System, by Michael Sweet (SAMS)
“Create a CUPS Print Server” [Hack #24]
3.16.218.216