Linux DevCenter    
 Published on Linux DevCenter (http://www.linuxdevcenter.com/)
 See this if you're having trouble printing code examples


Using Linux as a Small Business Internet Gateway

by Alexander Prohorenko
11/20/2003

The Internet is an integral part of the world's businesses. Practically any business that uses computers has Internet access. The need for connection is obvious — business and business-like correspondence with partners, access to databases, upgrading software, and so on.

As a rule, many small businesses were initially limited to one dialup connection from one computer. But electronic mail and access into the network very rapidly became necessary for multiple employees. This made the case to connect an entire office network to the global network. Of course, it would be inexpedient to buy each computer a separate modem and a separate Internet access account. What's needed is an Internet gateway, a separate computer through which everyone can share Internet access.

It still may be sufficient to use a dialup connection, and in time (if necessary) upgrade to a cable modem or DSL connection.

This article describes how to set up and configure such a gateway built on the Red Hat 9 operating system. In other Linux distributions and packages, path names, file names, and file formats and sizes can differ. The techniques are all the same, though.

Installing and Tuning The Necessary Services

More and more businesses use high-bandwidth connections through DSL or cable modems. Usually the hardware for these connections includes Ethernet connections. In this case, it's possible to skip tuning the dialup connection.

Tuning a Dialup Connection

To create a dialup connection, you will need pppd and wvdial. pppd supports connections via the PPP protocol. wvdial actually guides your modem in connecting to your ISP. If you've built your own Linux kernel, be sure that you've enabled PPP support. Red Hat builds this in to their pre-built kernels. Let's check for the existence of the appropriate packages for these utilities:

$ rpm -qa | grep ppp
ppp-2.4.1-10
$ rpm -qa | grep wvdial
wvdial-1.53-9

If necessary, please install these packages. Next, create a symbolic link to the actual device to which the modem is connected. For a modem connected to COM1, do the following:

# ln -s /dev/ttyS0 /dev/modem

To configure PPP, we need to append these lines to the /etc/ppp/options file:

# set remote computer as router by default
defaultroute
# work via modem
modem
# turn on RTS/CTS support for modem
crtscts
# get DNS addresses after connection from remote computer
usepeerdns

Now, let's configure wvdial. It usually comes with the wvdialconf utility to create the configuration, but sometimes it works incorrectly. That's why I suggest to create the configuration file manually. Edit or create the file /etc/wvdial.conf to contain:

; default 
[Dialer Defaults]
; modem init string
Init1 = ATZ
; ... up to 9 strings
Init2 = ATM1L2
; dial type (tone/pulse)
Dial Command = ATDP
; your ISP phone number
Phone = 555-12345
; login name and password for connection
Username = internet
Password = hard_password
; re-connect after break (off, if you don't need that)
Auto reconnect = on
; this argument is needed for ppp with version 2.4.x
New PPPD = on
; set this argument, if you use pppd for authorisation
Stupid Mode = on

To test your connection, obtain superuser privileges (by logging in as root or through the sudo command) and type:

# wvdial

To configure wvdial for multiple ISPs or phone numbers, you need to add special sections with specific descriptions, such as:

[Dialer MyProvider]
Phone = 555-43210
Username = dialup
Password = dial_password

In this case, to call the "MyProvider" ISP, pass its name on the command line:

# wvdial MyProvider

Arguments from additional sections overwrite the default arguments. You can see the full list of wvdial's arguments with the man wvdial command.

After running wvdial and performing any further authentication or connection with your ISP, your Linux server will be connected to network. To break the connection, you must send a signal to wvdial:

# kill `pidof wvdial`

You can easily automate the process of setting up and breaking dialup connections through cron.

Tuning the Proxy Server

The next step is to configure a proxy server. Usually, we use Squid. It's rather large and requires too much memory for proper and good work, but this could be compensated for with convenience in controlling, economizing your traffic (by about 30%), speeding web page access, and many other very useful features.

First, it almost goes without saying that we need to install it (if you don't have it yet):

# rpm -ihv squid-2.5.STABLE1-2.i386.rpm

Squid's configuration files live in the directory /etc/squid. It also contains a symbolic link, errors, which points to the directory storing all user error messages. You may need to modify this link to point to the appropriate language directory. For example, if you need messages displayed in the Russian language with Win-1251 encoding, use this command:

# rm -f /etc/squid/errors; ln -s /usr/lib/squid/errors/Russian-1251

Let's take care of common proxy configuration now. There is no need to describe the details of this or that argument — it's described in many other sources: a little bit in configuration files, a lot of in official documentation, FAQs, and many other articles about Squid. Instead, we will describe the minimal configuration changes necessary to run this service. Let's start by editing the file /etc/squid/squid.conf.

In the NETWORK OPTIONS section, set the argument for http_port to the IP address and port on which our proxy server will work.

http_port 192.168.0.1:3128

In the OPTIONS WHICH AFFECT THE CACHE SIZE section, the cache_mem argument defines the amount of RAM to allocate for cache objects. By default, it is 8MB. When you have too little memory (32MB or less) I suggest you decrease this value to 4MB. With a lot of memory, increase it.

Related Reading

Linux Network Administrator's Guide
By Olaf Kirch, Terry Dawson

The maximum_object_size argument defines the maximum size of any object to be stored in cache on disk. By default, it's 4096K. Depending on the free disk space in your /var/spool directory, you can decrease it, for example, to 1024K.

In the LOGFILE PATHNAMES AND CACHE DIRECTORIES section, the emulate_httpd_log argument defines the type and structure of the log file. By default, the value of this argument is off. In this case, the log file is rather specific. For example, time is set in Unix-style, so we need to use special utilities to convert it to any other readable view we need to use special utilities. When setting this argument on, the log file will appear the same as that of Apache httpd's log file. This argument may be critical when configuring your log analyzer. Some of them use one format, some of them use others.

In the OPTIONS FOR TUNING THE CACHE section, see the very interesting quick_abort_min, quick_abort_max, and quick_abort_pct arguments. They govern whether Squid should cache files that the user aborts downloading. By default, the first two arguments have a value of 16K and the third one, 95%. Incorrect arguments can create strange effects — even if users are not working with the proxy at all, it may still download some things into the cache. For small and slow networks, we suggest to set quick_abort_max to 1 or 2 and quick_abort_pct to 98 or 99.

In the TIMEOUTS section, the shutdown_lifetime argument defines the maximum timeout after the proxy stops. This governs when all open TCP user connections will be closed normally. By default, it's 30 seconds. For small networks, you can set it to 15 or even 10 seconds.

One of the most important sections is ACCESS CONTROLS. It defines ACLs — access control lists, a powerful and very useful Squid feature. By default, Squid is configured to allow proxy access only from the local user (through the localhost interface). In the simplest case, add the following line to the acl list:

acl mynetwork src 192.168.0.0/255.255.255.0

where mynetwork is the name of this ACL, src is a keyword, which defines the type of ACL (in our case it's a class C IP network), 192.168.0.0 is the network address, and 255.255.255.0 is the network mask. Please remember that when you define the IP address, you always need to define the network mask, even when defining only one IP address.

In list of arguments to the http_access parameter, you need to provide this access rule:

http_access mynetwork allow

The order of rules to http_access is very important. Rules are processed in order from the top to the first match. That's why you must add the above line you need to add exactly after:

http_access allow localhost

and before:

http_access deny all

Note that this specific example is correct only for the configuration file that comes with the package. In cases where the access rules have changed, the placement of the line that grants access may vary.

One very useful feature is that ACL lists can be loaded from files. Instead of writing addresses directly, you can set a pathname. For example:

acl myuserlist src "/etc/squid/acl/myusers.lst"

states that all IP addresses for myuserlist can be found in the file /etc/squid/acl/myusers.lst. The Squid process, which runs under the unprivileged user squid, should have at least read-only access to this file. This file must list all IP addresses, each on its own line.

Of course, ACL possibilities go far beyond only setting IP addresses. Lists can include dates and times, domain names, URLs (lists and regular expressions), ports, protocols, browsers, and HTTP methods, all with the help of ACL authorization. This gives for administrator powerful options to control proxy access. The Squid documentation and other articles give more details on ACLs. I will just add few useful recipes:

These examples show how easily we can solve very hard tasks with the help of Squid.

The MISCELLANEOUS section contains the very useful deny_info setting. It defines which error message to show the user when a http_access deny rule matches. Use it as:

deny_info ERROR_MESSAGE acl_name

where ERROR_MESSAGE is the name of a file that contains message text in HTML (without the tags </BODY> and </HTML>). This file should live in the /etc/squid/errors directory. The acl_name is the ACL that defines the active deny rule.

With our proxy configured, now we can run it:

# service squid start
init_cache_dir /var/spool/squid... Starting squid:      [  ok  ]

On the first startup, the special directory tree under /var/spool/squid will be built. This is the disk cache, in which objects will be kept. The init_cache_dir variable controls this directory. This operation can take up to few minutes (depending on your PC). The next startup will be much faster.

Now, if we connect to with our ISP with wvdial, our gateway is ready to serve the network. On client workstations, we need to configure browsers to work via our proxy server for the HTTP, HTTPS, and FTP protocols.

After testing our service, we will make it start by default:

# chkconfig squid on

Configuring the Firewall and NAT (Masquerading)

Our users are working with Internet already! But what should we do with email? There are a lot of free mail servers that work via the Web, but it can be uncomfortable to use a browser to read and write email, especially if you have a lot of mail boxes. Newsgroup access can also be good.

From the other side, a freshly built server sounds not very secure. Of course, a correctly configured proxy will never allow any "alien" to access it, but all other services are not so well-protected.

It seems that there is nothing in common to these questions, but they can be solved with one tool — a firewall. Linux distributions often ship with two configuration packages: ipchains (old) and iptables (new). Below, we will examine iptables, which Red Hat uses by default.

Let's start from security. The /etc/sysconfig directory contains the file iptables. This file is in the special iptables-save and iptables-restore utilities format. By default, after installation, if we choose the average level of protection, this will be created by the lokkit utility.

# Firewall configuration written by lokkit
# Manual customization of this file is not recommended.
# Note: ifup-post will punch the current nameservers through the
#       firewall; such entries will *not* be listed here.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:RH-Lokkit-0-50-INPUT - [0:0]
-A INPUT -j RH-Lokkit-0-50-INPUT
-A FORWARD -j RH-Lokkit-0-50-INPUT
-A RH-Lokkit-0-50-INPUT -i lo -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp --dport 0:1023 --syn -j REJECT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp --dport 2049 --syn -j REJECT
-A RH-Lokkit-0-50-INPUT -p udp -m udp --dport 0:1023 -j REJECT
-A RH-Lokkit-0-50-INPUT -p udp -m udp --dport 2049 -j REJECT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp --dport 6000:6009 --syn -j REJECT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp --dport 7100 --syn -j REJECT
COMMIT

This configuration file has three separate logical divisions. The default policy comes first, allowing any INPUT, FORWARD, and OUTPUT packets to pass through. Next comes the special rule RH-Lokkit-0-50-INPUT. Though this is not necessary in our simple example, the technique is very flexible and it is a good idea to use it in larger snippets. The final part contains one rule per line. Packets are filtered against the rules from the top to the bottom, looking for the first match. One common mistake is to create an allow rule after deny rule. That's why, when creating rules, we first set allow rules for hosts, then deny rules for hosts, then allow rules for networks, and finally deny rules for networks.

Our generated, mid-level security configuration file allows all packets on the local interface, denies inbound packets on ports below and including 1023 (where only root can use them), and denies all X Window system and font server ports. If the connection originated on the local site, the filter will allow all packets.

This is a very reasonable workstation configuration, but it can be improved for a server-gateway. Let's start our modifications now. We'll assume that our internal network is 192.168.0.0/255.255.255.0 on Ethernet interface eth0, with a gateway address of 192.168.0.1.

Now we will configure how users will work with mail and news servers that require active, back-and-forth external connections. We'll use NAT, often called "masquerading" in the Linux world. It converts IP packets that come from internal machines so that they appear to come from the gateway. Reply packets are back-converted.

First, we need to allow global packet forwarding. Either execute the following command as a root user:

# echo 1 > /proc/sys/net/ipv4/ip_forward

or this command, again as a root user:

# sysctl -w net.ipv4.ip_forward=1

To enable forwarding on boot, modify the /etc/sysctl.conf file. Change this line:

net.ipv4.ip_forward = 0

to:

net.ipv4.ip_forward = 1

The next step is to configure iptables. As a rule, all mail services use three TCP ports: 25 for outgoing mail through SMTP, 110 for incoming mail through POP3, and 143 for incoming mail through IMAP4. NNTP, for newsgroups, uses port 119. (See /etc/services for the more.) To allow connections through those ports, we need to add rules to /etc/sysconfig/ipchains.

It may also be necessary to configure DNS on user workstations, as far as our mail clients need this. If we know the IP addresses of our mail servers, we can fill in the hosts files: /etc/hosts for Unix, c:\windows\hosts for Windows 9x/ME, and c:\winnt\system32\drivers\etc\hosts for NT/W2k/XP — a strange place, isn't it?

The order of these mail rules is very important, as usual. Behold our modifications:

# Simple firewall for internet gateway
*filter
:INPUT ACCEPT [0:0]

# Deny forward for security reasons
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:RH-FW-INPUT - [0:0]

# Redirect all input-forwarded packets to our special rule
-A INPUT -j RH-FW-INPUT

# Accept all packets for local interface (lo)
-A RH-FW-INPUT -i lo -j ACCEPT

# Define custom rules
# This part equal to some part from generated by Lokkit utility
-A RH-FW-INPUT -p tcp -m tcp --dport 0:1023 --syn -j REJECT
-A RH-FW-INPUT -p tcp -m tcp --dport 2049 --syn -j REJECT
-A RH-FW-INPUT -p udp -m udp --dport 0:1023 -j REJECT
-A RH-FW-INPUT -p udp -m udp --dport 2049 -j REJECT
-A RH-FW-INPUT -p tcp -m tcp --dport 6000:6009 --syn -j REJECT
-A RH-FW-INPUT -p tcp -m tcp --dport 7100 --syn -j REJECT

# Allow access to mail/news/DNS
-A FORWARD -s 192.168.0.0/24 -p tcp -m tcp --dport 25 -j ACCEPT
-A FORWARD -s 192.168.0.0/24 -p tcp -m tcp --dport 110 -j ACCEPT
-A FORWARD -s 192.168.0.0/24 -p tcp -m tcp --dport 143 -j ACCEPT
-A FORWARD -s 192.168.0.0/24 -p tcp -m tcp --dport 119 -j ACCEPT
-A FORWARD -s 192.168.0.0/24 -p tcp -m tcp --dport 53 -j ACCEPT
-A FORWARD -s 192.168.0.0/24 -p udp -m udp --dport 53 -j ACCEPT
COMMIT

# Enable masquerading
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A POSTROUTING -p tcp -s 192.168.0.0/24 -o ppp0 -j MASQUERADE
-A POSTROUTING -p udp -s 192.168.0.0/24 -o ppp0 -j MASQUERADE
COMMIT

Don't forget to add commands for loading the kernel modules that allow masquerading:

# touch /etc/rc.d/rc.modules
# chmod +x /etc/rc.d/rc.modules
# ln -s /etc/rc.d/rc.modules /etc/rc.modules
# cat > /etc/rc.d/rc.modules
 modprobe ip_conntrack
 modprobe ip_conntrack_ftp
 modprobe iptable_nat
 modprobe ip_nat_ftp
^D

Execute the rc.modules (to avoid rebooting):

# /etc/rc.d/rc.modules

Please note that the forward policy is now DENY. This is for security reasons: to deny illegal IP packets.

With the configuration files changed, we have to run and enable the auto-loading of iptables, now.

# service ipchains start
Reseting all current rules and user queries                [  OK  ]
Cleaning all current rules and user queries                [  OK  ]
Loading rules iptables                                     [  OK  ]

# chkconfig iptables on

Sometimes starting iptables can break some services. In this, case I suggest turning on logging for each REJECT or DROP rule. Replace -j DROP or -j REJECT with -j LOG --log-prefix "short comment". This will add messages to the system log in /var/log/messages for packets that would be blocked, allowing you to craft appropriate allow rules.

These techniques can also allow certain users to access other services outside of the network. Try to avoid this as much as possible; exceptions decrease the total security level of your network.

Installing a Mail Service

Over time, our traffic will grow. Users will become upset with low speeds for downloading files, loading mail, and so forth. While we'll need to consider increasing our bandwidth, we can help our users with their mail even now by installing a local mail service — a SMTP and POP3 server. For proper working we will require a static IP address, available from our ISP. It's cheap, and we shouldn't worry about that too much. We also will need our own mail domain.

Many SMTP servers use Sendmail and many services also use it. I personally do not recommend using Sendmail in this project. It's difficult to configure for the uninitiated and requires many patches and constant security vigilance. Fortunately, we have many alternatives.

Postfix comes with our distribution. Let's install it, configure it for POP3 and IMAP services, and delete Sendmail:

# rpm -ihv postfix-1.1.11-11.i386.rpm
Preparing...             ########################################### [100%]
1:postfix                ########################################### [100%]

# rpm -ihv imap-2001a-18.i386.rpm
Preparing...             ########################################### [100%]
1:imap                   ########################################### [100%]
# rpm -e sendmail

We will start with the easiest parts, configuring POP3 and IMAP4. We just need to run the services and configure access rights. Because these services run from the xinetd super-daemon, we must edit both /etc/xinetd.d/ipop3 and /etc/xinetd.d/imap to change the line:

disable = yes

to

disable = no

Then reload the super-daemon:

# service xinetd reload

As far as super-daemon services working within tcp_wrappers, we allow access only for computers on our network to these services. Add these lines to /etc/hosts.allow:

ipop3d: 192.168.0
imapd: 192.168.0

By the way, it's good to check for the following lines in /etc/hosts.allow and /etc/hosts.deny, respectively:

ALL: 127.0.0.1

and

ALL: ALL

Usually, both files are empty, allowing access to all services run by the super-daemon. This is likely not what you want. The above lines deny access to all services except from the local machine; we added exceptions for POP3 and IMAP. Secure everything first, then ease up as necessary.

The hard part is configuring Postfix. This software has so many abilities, another article would be required. (Editor's note: see "Postfix: An Easy to Use and Secure MTA" for just that.) Postfix includes nice documentation with answers to the most frequent questions. Here, we will describe only the basic configuration we need to run the service properly.

All Postfix configuration files live in the directory /etc/postfix. All files mentioned below are found there by default, if not otherwise specified.

For our comfort, suppose that our network gateway address is 192.168.0.1, our external address from the ISP is 123.123.123.123, and our mail domain is ourcompany.com.

Let's start by editing a few settings in main.cf.

In the INTERNET HOST AND DOMAIN NAMES section, the myhostname argument defines the host name (of our gateway). In our case, this could be mail.ourcompany.com or even just ourcompany.com. If the latter, you also need to define the mydomain setting to set the mail domain of the organization. Use ourcompany.com.

In the SENDING MAIL section, the myorigin argument defines the domain name of sender to use when sending mail from the local host (our gateway). Use $mydomain.

In the RECEIVING MAIL section, we can set inet_interfaces to all, causing Postfix to accept connections from any existing network interface on the gateway. The mydestination argument defines for which domains to accept mail as local. We want mail for john@mail.ourcompany.com, john@localhost.ourcompany.com, and john@ourcompany.com to be delivered to the local user john. Use the following line:

mydestination = $myhostname, localhost.$mydomain, $mydomain

Mail can be sent two ways: directly to the mail server of the receiver or via the mail server of our provider. The second approach is better, in our case — we'll send all outgoing mail to our provider, letting the provider worry about where to send it. That's why the relayhost argument in the INTERNET OR INTRANET section is important. Its value should be the name of our ISP's mail server:

relayhost = mail.provider.net

In the REJECTING UNKNOWN LOCAL USERS section, uncomment the line:

local_recipient_maps = $alias_maps unix:passwd.byname

This defines the location of the list of local users for whom we need to receive mail.

That's all for main.cf.

Now, we need to modify the aliases file. It consists of a list of mail aliases. Find the line:

root: postmaster

and change postmaster to the name of the administrator's account. Postfix will not deliver mail to the root for security reasons. Instead, we'll choose a lucky normal user to receive all of root's mail instead.

Compile this text file into a Postfix-readable database file:

# postalias aliases

Remember to do this every time you change the aliases file.

One thing remains. The part of Postfix that communicates over TCP works in a chrooted environment, for increased security. That is, for this operation, the root of the file system isn't the real root directory but another directory (in this case, /var/spool/postfix). You'll need to copy your /etc/passwd file into /var/spool/postfix/etc (don't use a symlink, just copy it!). Remember to do this each time you add a new user, otherwise mail for this user will not be received. Nevertheless, we can easily automate this process.

That seems to be everything that's needed. It's time to run and install our mail service:

# service postfix start
Starting postfix:                                          [  OK  ]

# chkconfig postfix on

We'll need to reconfigure the firewall to allow users to use these mail services:

# Simple firewall for internet gateway
*filter
:INPUT ACCEPT [0:0]

# Deny forward for security reasons
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:RH-FW-INPUT - [0:0]

# Redirect all input-forwarded packets to our special rule
-A INPUT -j RH-FW-INPUT

# Accept all packets for local interface (lo)
-A RH-FW-INPUT -i lo -j ACCEPT

# Define custom rules
# Access to mail services
-A RH-FW-INPUT -p tcp -m tcp --dport 25 -j ACCEPT
-A RH-FW-INPUT -s 192.168.0.0/24 -p tcp -m tcp --dport 110 -i eth0 -j ACCEPT
-A RH-FW-INPUT -s 192.168.0.0/24 -p tcp -m tcp --dport 143 -i eth0 -j ACCEPT

# This part equal to some part from generated by Lokkit utility
-A RH-FW-INPUT -p tcp -m tcp --dport 143 -j ACCEPT
-A RH-FW-INPUT -p tcp -m tcp --dport 0:1023 --syn -j REJECT
-A RH-FW-INPUT -p tcp -m tcp --dport 2049 --syn -j REJECT
-A RH-FW-INPUT -p udp -m udp --dport 0:1023 -j REJECT
-A RH-FW-INPUT -p udp -m udp --dport 2049 -j REJECT
-A RH-FW-INPUT -p tcp -m tcp --dport 6000:6009 --syn -j REJECT
-A RH-FW-INPUT -p tcp -m tcp --dport 7100 --syn -j REJECT

# Allow access to external mail/news/DNS
-A FORWARD -s 192.168.0.0/24 -p tcp -m tcp --dport 25 -j ACCEPT
-A FORWARD -s 192.168.0.0/24 -p tcp -m tcp --dport 110 -j ACCEPT
-A FORWARD -s 192.168.0.0/24 -p tcp -m tcp --dport 143 -j ACCEPT
-A FORWARD -s 192.168.0.0/24 -p tcp -m tcp --dport 119 -j ACCEPT
-A FORWARD -s 192.168.0.0/24 -p tcp -m tcp --dport 53 -j ACCEPT
-A FORWARD -s 192.168.0.0/24 -p udp -m udp --dport 53 -j ACCEPT
COMMIT

# Enable masquerading
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A POSTROUTING -p tcp -s 192.168.0.0/24 -o ppp0 -j SNAT --to-source 123.123.123.123
-A POSTROUTING -p udp -s 192.168.0.0/24 -o ppp0 -j SNAT --to-source 123.123.123.123
COMMIT

There are two important changes in our firewall settings. First, we've given access from everyone to our SMTP server and from all of our local network to POP3 and IMAP services. Second, we've changed the masquerading rules slightly. There are two predefined rulesets, SNAT and MASQUERADE. SNAT is preferable for systems with a static IP address on an external interface. MASQUERADE is more often used for systems with dynamic addresses.

Conclusion

We've explored the installation and configuration of Linux as an Internet gateway for a small network. The next article will explain the installation and minimum necessary configuration for DNS and the Apache web server, as well as different monitoring services — counting traffic, load balancing, and analyzing logfiles.

Alexander Prohorenko is a certified professional, who holds Sun Certified System Administrator and Sun Certified Java Programmer certifications.


Return to the Linux DevCenter.

Copyright © 2009 O'Reilly Media, Inc.