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.
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.
To create a dialup connection, you will need
pppd supports connections via the PPP protocol.
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
; 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 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.
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
# kill `pidof wvdial`
You can easily automate the process of setting up and breaking dialup
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.
NETWORK OPTIONS section, set the argument for
http_port to the IP address and port on which our proxy server
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,
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.
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.
OPTIONS FOR TUNING THE CACHE section, see the very
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
TIMEOUTS section, the
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 mynetwork src 192.168.0.0/255.255.255.0
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
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
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
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
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:
Blocking banners and porn sites:
acl allow_url url_regex "/etc/squid/acl/allow_url" acl deny_url url_regex "/etc/squid/acl/deny_url" ..... http_access allow allow_url http_access deny deny_url
deny_url contains regular expressions that define
denied sites, for example:
^http://.*porno.* ^http://.*xxx.* ^http://.*\.linkexchange\.com/cgi-bin
allow_url contains exceptions to this list, for example:
Support running some applications (e.g., ICQ, Odigo, etc.) over HTTPS.
By default, Squid denies HTTPS requests (and the
over any port, except for ports 443 and 563. As a result, some applications that use
HTTPS over their own ports will not work. The obvious solution is to comment
out the line:
http_access deny CONNECT !SSL_ports
We probably shouldn't do that, because it will open the proxy to access from any utility that uses SSL. It's better to find out to which port the specific application uses. As an example, ICQ uses port 5190. We can modify the line:
acl SSL_ports port 443 563
adding port 5190:
acl SSL_ports port 443 563 5190
After restarting Squid, everything will work properly.
Limit access based on time. In some cases we need to allow access through the proxy only during specific times of the day. The solution, as always, is easy:
# Define access time: acl worktime_am time MTWHF 8:00-11:00 acl worktime_pm time MTWHF 12:00-16:40 # Define access lists: acl time_unlimited src "/etc/squid/time_unlimited.list" acl time_limited src "/etc/squid/time_limited.list" ... # Set rules: http_access allow time_unlimited http_access allow time_limited !worktime_am !worktime_pm http_access deny all
These examples show how easily we can solve very hard tasks with the help of Squid.
MISCELLANEOUS section contains the very useful
deny_info setting. It defines which error message to show the user
http_access deny rule matches. Use it as:
deny_info ERROR_MESSAGE acl_name
ERROR_MESSAGE is the name of a file that contains
message text in HTML (without the tags
</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
After testing our service, we will make it start by default:
# chkconfig squid on
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
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-restore utilities format.
By default, after installation, if we choose the average level of protection,
this will be created by the
# 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
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
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
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
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
/etc/services for the more.) To allow connections
through those ports, we need to add rules to
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
rc.modules (to avoid rebooting):
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
# 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
iptables can break some services. In this, case I suggest
turning on logging for each
-j DROP or
-j REJECT with
--log-prefix "short comment". This will add messages to the system log
/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.
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
disable = yes
disable = no
Then reload the super-daemon:
# service xinetd reload
As far as super-daemon services working within
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:
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 220.127.116.11, and our mail domain is ourcompany.com.
Let's start by editing a few settings in main.cf.
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
SENDING MAIL section, the
defines the domain name of sender to use when sending mail from the local host
(our gateway). Use
RECEIVING MAIL section, we can set
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
email@example.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
REJECTING UNKNOWN LOCAL USERS section, uncomment the
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:
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
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
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 18.104.22.168 -A POSTROUTING -p udp -s 192.168.0.0/24 -o ppp0 -j SNAT --to-source 22.214.171.124 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.
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.