SecurityEnlisting Java in the War Against Email Viruses

Enlisting Java in the War Against Email Viruses

Java Programming Notes # 2180


Preview


A common scenario

Consider the following scenario.  To use the common jargon, you
decide that it is time “check your email.”  This produces
a list of about twenty messages on the screen in your favorite email
client program.  By quickly scanning the list, you conclude that
about half are SPAM and the other half are important messages from
business associates.

The good news

You also notice that there is a message from an old friend that you
haven’t heard from in awhile, so you decide to open and read that
message before getting down to the business of the day.

The bad news

You double click on message from your friend.  However, before it
becomes readable, your virus program pops up a dialog box telling you
in effect that it has found a virus in the message and that it was
unable
to clean the file.  Therefore, it has quarantined the file, making
it
inaccessible.

Shucks, you say to yourself, making a mental note to call your
friend later in the day to tell him that he sent you a message
containing a virus.

Why did your friend send you a virus?

Your friend probably didn’t purposely send you a virus.  Rather,
your
friend’s computer has probably become contaminated with a virus that is
replicating itself by sending virus messages to everyone in your
friend’s email address book.  Even messages from trusted
friends can’t be
trusted.

Now for the really bad news

When you attempt to open and read one of the important business
messages that you have just received, you discover that it is also
inaccessible.  You quickly discover that every message in your
inbox, including the important business messages, has become
inaccessible.

How can this happen? 

Only one message contained a virus.  Why did all of the messages
in your inbox suddenly become inaccessible?

The answer is simple once you understand it

Many email clients present you with a display that looks much like the
display of folders containing files on your disk.  The email
client allows you to organize your messages by storing them in folders,
one of which is often named inbox.  The visual suggestion
is that each message is a separate file, where groups of related files
are contained in folders or directories on the disk.

Looks can be misleading

However, with many email clients, looks can be misleading.  Many
email client programs combine groups of related messages into a single
file.  With Netscape Communicator, for example, there really is no
folder on the disk named inbox.  Rather, there is a
single file named inbox, and all the messages in your inbox are
concatenated into that single file.

(If you are interested in detailed format information
for files of the type used by Netscape Communicator, go here.)

Many messages in each file

Netscape Communicator concatenates all of the messages that you store
in each mail folder into a single file whose name matches the name of
the folder.  Then they create another file with a similar name
that provides an index into the file containing the messages. 
They
put these files into an actual disk folder deep inside the directory
tree that contains lots of Netscape material.

(The organization is actually somewhat more complicated
than this,
but hopefully you get the idea.  Individual messages are not in
individual files with Netscape Communicator.)

Other email clients behave similarly

Although I don’t have any personal experience to confirm this, I’ve
been told that other popular email clients group messages into files on
an even larger scale, possibly causing multiple mail folders and their
messages to be included in a single file.

Virus checker configuration

When I configure “File System Realtime Protection” for my virus
checker, I have the following options:

Primary Action when a virus is detected:

  • Clean virus from file
  • Quarantine infected file
  • Delete infected file
  • Leave alone (log only)

If Primary Action fails:

  • Quarantine infected file
  • Delete infected file
  • Leave alone (log only)

My configuration

I have my virus checker set to the first choice in both cases, (which
is the default configuration).
  Therefore, if my virus checker
is unable to clean a virus from my inbox file, it is well
within its rights to quarantine the entire file.  Unfortunately,
that causes all the messages currently in the inbox to become
inaccessible.

(The only good news at this point is that you can still
identify the senders.  You can send a message to those parties
asking them to send you another copy of the message.)

An even worse scenario

At worst, the above scenario will cause you to lose a few email
messages,
most of which are probably recoverable by requesting another copy from
the sender.

Now consider another scenario that can potentially cause you to lose
vast amounts of critical business information, unrelated to email
messages.

You avoided quarantine

Assume that somehow you managed to keep the virus checker from putting
your inbox file into quarantine when you received a message
containing a virus.  (Perhaps you had it
turned off at the time).

You have learned not to open email attachments that you weren’t
expecting.  While reviewing the subjects of the messages in the
inbox, you noticed the suspicious message carrying the virus, and you
simply
deleted the message containing the virus without opening it.

Another surprise may be in store

At least for users of Netscape Communicator (and probably other
products as well),
when you “delete” a message, it really
doesn’t get deleted from the system.  Rather, it simply gets moved
into another folder (file), typically named Trash.

Always do your backups

Before leaving work that day, without “emptying the Trash” in
your email client, you dutifully perform an incremental backup, which
saves all the material on your disk that has changed since the last
incremental backup.  Guess what?  The Trash file has
changed. You saved it.  It contains a virus, and you have just
created an incremental backup file containing a virus.

You may catch it the next time you scan

If you leave a copy of the incremental backup file on your disk, the
next time you do a full virus scan on the disk, you will be notified
that the incremental backup file contains a virus.  Depending on
several factors, that incremental backup file may or may not have been
rendered
worthless for restoration purposes.  When an incremental backup
file is rendered worthless, the entire backup scheme is rendered
worthless.

You are lucky if it turns out this way.  The solution is to empty
the trash in your email client and start a new backup series beginning
with a full backup.

You may not be so lucky

Even worse, you may not have left a copy of the incremental backup file
on the disk, and the first time you learn that it contains a virus may
be when you try to use it later to restore your data after a disk
crash.  That is a very bad time to learn that an email virus has
rendered your backup scheme worthless for restoration purposes.

With this scenario, a simple email virus can cause you to lose vast
amounts of critical business information.

And worst of all

And perhaps worst of all is the case where you accidentally allow an
email-borne virus to get lose inside your computer.  When this
happens, the virus can begin replicating itself on every computer on
your in-house network, not to mention sending itself to all of your
customers whose email addresses are contained in your address book.

Why am I telling you this?

I am telling you this to drive home the point that email-borne viruses
can be
far more serious than just being a nuisance.  Not only can they
cause you to lose important business email messages, they can also
cause you to lose a wide-ranging variety of business information in the
event of a need to restore business data from backup files.

(Because of this possibility, I no longer do incremental
backups, even though they are much faster to do.  I’m not certain
of the correct name for this procedure, but each time I do a
backup, I save every file that has changed since the full backup that
began the series.  Thus, only the first and last backup files are
required to completely restore the system.

In addition, I regularly scan my backup files
for viruses.  If I find that my latest backup file contains a
virus, I can delete the virus from the disk and do another backup on
everything that has changed since the full backup.  That gives me
a way to recover without having to start over.)

No virus messages allowed

As illustrated above, it is extremely important that you don’t allow
messages with viruses to
get into your email data structure.  The purpose of this article
is to show you one way to accomplish that goal.

A simple and inexpensive scheme

The scheme that I will describe is very simple and also very
inexpensive.  The resources required consist of three computer
programs and two email accounts, one public and one private.

(By private, I mean a secret email account known only to
you.  You must not divulge the email address on the account to
anyone, including your best
friends.  It is extremely important that your secret email address
never exist in the email address book on any computer anywhere in the
world.  This is particularly true for address books belonging to
trusted friends whose email messages you are most likely to read.)

A second email account

A second email account may cost you two or three dollars per month, if
it costs you anything at all.  Many ISPs provide you with several
email accounts at no extra charge.

Two of the programs are free

Two of the required programs are free, because I am going to publish
them in this article.  The third required computer program is a
good virus checker program, which you should already have anyway.

The operational scheme

The operational scheme is very simple.  For now, let’s call the
two programs that I will provide program A and program B.

You never use your email client program to download
email messages
from your public email account.  Rather, you run program A to
download the messages for you.

A separate file for each message

When you run program A, it will download all of the messages on the
server and write each of those messages into a separate file in a
specified folder on your disk.  It is important to note that at
this point, the messages are still separated from one another.  A
virus in one message cannot corrupt another message.

(On my system with a cable modem, I can download one
hundred messages in just a few seconds, so this process is very fast.)

Scan the message files for viruses

Next you use your virus checker program to scan all of the message
files in that folder, either cleaning or removing any that contain a
virus.

(My advice would be to totally remove the contaminated
file to avoid any
possibility of creating an invalid message format in the file. )

On my system, which is relatively slow, only a few seconds are required
to scan one hundred messages.

Now forward the remaining clean messages

The next step is to run program B, which forwards the remaining
messages to your secret email account.  If you did the virus check
(and you keep your virus checker program up to date), all of
these messages should be free of viruses.

This process takes a little longer than the other two.  My system,
running through a
cable modem, requires about three or four seconds to forward each
message.  Thus, one hundred messages require five or six minutes
to forward.

No need to wait

However, you don’t have to wait until the third step described
above is complete to view your messages.  Simply open your email
client program and start downloading and reading messages from your
secret email account.

As soon as the first message has been forwarded to the secret email
account, it is available for downloading and reading.  For the
next few minutes,
messages will be forwarded to your secret email account much faster
than you can possibly read them.

Worth the extra time and effort

Unless you have a very large number of messages, a very slow computer,
or a very slow Internet service, the steps required before you can
start reading your messages should take less than a minute to
complete.  This is a small price to pay for assurance that your
email data structure is free of viruses.

Viewing tip

You may find it useful to open another copy of this lesson in a
separate browser window.  That will make it easier for you to
scroll back and forth among the different listings and figures while
you are reading about them.

Supplementary material

I recommend that you also study the other lessons in my extensive
collection of online Java tutorials.  You will find those lessons
published at Gamelan.com
However, as of the date of this writing, Gamelan doesn’t maintain a
consolidated index of my Java tutorial lessons, and sometimes
they are difficult to locate there.  You will find a consolidated
index at www.DickBaldwin.com.

Operational
Discussion

The operation of each of the two programs is very simple.  The
names of the two programs are VirPro01a and VirPro01b,
corresponding respectively to program A and program B in the
hypothetical case discussed above.

Starting the program named VirPro01a

Once you have a compiled version of VirPro01a on your system,
and you have created a working directory, you start the program running
by entering the following at the command line (for convenience, I
use a batch file with a shortcut on the desktop to accomplish this):

java VirPro01a pubServer userName
password

The command line parameters identify your public email server, your
user name, and your password on that email server.  (This is
the same information that you normally provide when setting up an email
client program.)

This information makes it possible for the program to connect to your
public email server and to download the messages currently stored there.

(If you are uncomfortable with this handling of the
password, you could modify the program to elicit the password at
runtime.  See JPasswordField in the Java documentation.)

The user interface for VirPro01a

Figure 1 shows the user interface for VirPro01a.  This
simple interface consists of a button and a text area.

VirPro01a user interface

Figure 1 VirPro01a user interface

You start the download by pressing the Start button. 
Information is displayed in the text area as the program
executes. 
Figure 1 shows that the program has downloaded fifty messages, (with
corresponding message numbers),
from the public email server.

The working folder named Messages

As you will see when we examine the code for VirPro01a, with my
setup, these messages are stored in a folder named Messages
However, you can modify the program to store them anywhere you choose.

(Note that no messages are deleted from the email server
by the program named VirPro01a.)

Run the virus checker

After VirPro01a finishes downloading the messages and storing
them in individual files, the next step is to run your virus checker on
all the files in the working folder, removing any that contain a virus.

Starting the program named VirPro01b

Once you have a compiled version of VirPro01b on your system,
and you have created an archive directory in addition to the working
directory, you start the program named VirPro01b by
entering the following at the command line (with no line breaks):

java VirPro01b pubServer userName password secretServer smtpServer

In this case, the command line parameters identify:

  • Your public email server
  • Your user name on the public server
  • Your password on the public server
  • Your secret email address
  • The SMTP server on which you are authorized to send email
    messages.

Once again, this is the information that you typically enter when
setting up a standard email client program.

What is this information used for?

This information makes it possible for the program to:

  • Forward the clean email messages from the working folder (named
    Messages in my case)
    to your secret email account.
  • Delete the corresponding messages from the public email server
    once they have been forwarded to the secret email account.

When messages are deleted from the public email server, the
corresponding message files are automatically moved from the working
folder to the archive folder.

(You will probably want to delete the message files from
the archive folder periodically to free up disk space.)

Why have an archive folder?

With my setup, I tag the subject line of each forwarded message with
the sequential message numbers from the public email server.  (You
can modify the program to substitute any tag that you choose, including
no tag at all.)
  Tagging with message numbers makes it easy
for me to confirm that every message arrived safely at the secret email
account.

If a message doesn’t show up …

In the unlikely event that a message fails to be successfully forwarded
to the secret email account, there is a backup copy in the archive
folder.  You can view the backup copy with an ordinary text editor.

If the message was composed in plain text, you will be able to read it
in its entirety using a text editor.

If the message was composed using HTML text, you will be able to read
it using a text editor, but you will have to ignore the HTML tags.

SPAM messages

If the message is a SPAM message, it is not likely that you will be
able to make much sense out of it with a text editor.  It will
probably contain fairly complex HTML code with hundreds of random
words, phrases, and sentences inserted to thwart automatic spam
blocking software.  In addition, the HTML will probably contain
links to various web sites where most of the substance of the SPAM
message is presented.  If you really want to read it, however, you
can probably copy the HTML, paste it into an empty HTML file, and open
it in your browser.

Base64 encoding

If the message was composed in non English characters using Base64
encoding, you won’t be able to read it at all without running it
through a Base64 decode program.

(I have published other tutorials that explain how to do
Base64 decoding using the sun.misc.BASE64Decoder class.  You can
also search for information about Base64 decoding on Google.)

Keeping a backup copy of the message

In any event, I consider it a good idea to keep a backup copy of the
message files for a couple of days after forwarding them to the secret
email account, just in case I need them for some reason.

The user interface for VirPro01b

Figure 2 shows the simple user interface for VirPro01b.

VirPro01b user interface

Figure 2 User interface for VirPro01b

The user presses the Start button to cause the
message forwarding process to begin.

The screen shot in Figure 2 was captured at the point where seven of
the fifty messages from Figure 1 had been forwarded to the secret email
account.

Once all of the messages in the working directory have been forwarded
to the secret email account, the Delete button is
enabled.  This makes it possible for the user to delete the
messages
from the server, and to cause the corresponding message files to be
moved from the working folder to the archive folder.

Another safety feature

If the communication portion of the program detects any problems (throws
any exceptions)
in
forwarding a message, that message will not be included on the list of
messages to be deleted from the server and moved from the working
folder to
the archive folder.  Thus, when the deletion operation is
complete, those message files will still be on the server and will
still be in the working folder.

You can rerun VirPro01b
in another attempt to forward the remaining messages.  Depending
on the nature of the problem (such as a timeout due to network
congestion),
the second attempt to forward a message may or may not
be successful.

(I have found this to occur occasionally under very poor
network conditions.)

Program Code

Program VirPro01a

I will begin by
explaining the program code for the program named VirPro01a
Portions of this discussion will be very brief because I have
previously explained similar code in a lesson entitled
Enlisting
Java in the War Against SPAM, Part 1, The Communications Module
.


The VirPro01a
program is designed to be used
with a POP3 email
server as the public email server.

(The server for the secret
email account can be of any type for which you can obtain a message
viewer.  For example, it could be a typical WebMail server.)

For technical information on the POP3 message transfer protocol, see
RFC 1725 at
http://www.cis.ohio-state.edu/htbin/rfc/rfc1725.html.

This program was tested using SDK 1.4.2 under WinXP.

Will discuss in fragments

As is my custom, I will discuss the program in fragments.  A
complete listing of the program is provided in Listing 40 near the end
of the lesson.

Instance variables

The beginning of the VirPro01a class, and a list of instance
variables are shown
in Listing 1.

class VirPro01a extends Frame{
String dataPath = "./Messages/";

int numberMsgs = 0;
int msgCounter = 0;
int msgNumber;
String uidl = "";//unique POP3 msg ID
BufferedReader inputStream;
PrintWriter outputStream;
Socket socket;
String pathFileName;

Listing 1

The variable named dataPath contains a reference to the local
working folder where messages are stored awaiting virus scanning and
forwarding to the secret email account.

You might want to use a different folder for this purpose. If so,
simply
provide the path and folder name as a String.  As you can
see, my
working folder, named Messages is specified relative to the
folder that contains the class files for the program.  You could
use an absolute path rather than a relative path if you choose.

The remaining instance variables in Listing 1 are simply working
variables used by the program for various purposes.

The main method

The main method, shown in Listing 2, confirms the correct
number of command line parameters, and uses those parameters to
instantiate an object of the VirPro01a class.

  public static void main(String[] args){
if(args.length != 3){
System.out.println("Usage: java VirPro01a "
+ "pubServer userName password");
System.exit(0);
}//end if

new VirPro01a(args[0],args[1],args[2]);
}//end main

Listing 2

The constructor

The constructor begins in Listing 3.

  VirPro01a(String server,String userName,
String password){
int port = 110; //pop3 mail port
try{
//Get a socket, connected to the
// specified server on the specified
// port.
socket = new Socket(server,port);

//Get an input stream from the socket
inputStream = new BufferedReader(
new InputStreamReader(
socket.getInputStream()));

//Get an output stream to the socket.
outputStream = new PrintWriter(
new OutputStreamWriter(
socket.getOutputStream()),true);

//Display the msg received from the
// server on the command line screen
// immediately following connection.
String connectMsg = validateOneLine();
System.out.println("Connected to server "
+ connectMsg);

//The communication process is now in the
// AUTHORIZATION state. Send the user
// name and password to the server.
//Commands are sent in plain text, upper
// case to the server. Some commands
// require an argument following the
// command, as is the case with USER.
//Send the command.
outputStream.println("USER " + userName);
//Get response and confirm that the
// response was +OK and was not -ERR.
String userResponse = validateOneLine();
//Display the response on the command-
// line screen.
System.out.println("USER " + userResponse);
//Send the password to the server
outputStream.println("PASS " + password);
//Validate the server's response as +OK.
// Display the response in the process.
System.out.println(
"PASS " + validateOneLine());
}catch(Exception e){e.printStackTrace();}

Listing 3

The code in Listing 3 sets up the communication path with the public
email server. 

This code is almost identical to the code that I explained in the earlier
lesson, so I will let the comments in Listing 3, plus the
discussion in the earlier lesson suffice.

A WindowListener

Listing 4 uses an anonymous class to instantiate and to register a WindowListener
object to service the close button in the upper right corner of
the Frame in Figure 1.

    this.addWindowListener(
new WindowAdapter(){
public void windowClosing(WindowEvent e){

//Terminate the session with the
// server.
outputStream.println("QUIT");
String quitResponse =
validateOneLine();
//Display the response on the
// command line screen.
System.out.println(
"QUIT " + quitResponse);

try{
socket.close();
}catch(Exception ex){
System.out.println("n" + ex);}

System.exit(0);
}//end windowClosing
}//end WindowAdapter()
);//end addWindowListener

Listing 4

Once again, this code is almost identical to the code that I explained
in the earlier
lesson, so I will let the comments in Listing 4, plus the
discussion in the earlier lesson suffice.

Final local variables

Listing 5 declares and initializes two local variables in the
constructor.

    final Button startButton =
new Button("Start");
final TextArea textArea =
new TextArea(20,50);

Listing 5

These two local variables contain references to the Button and
the TextArea shown in Figure 1.

(Note that these two local variables must be
marked final because they are accessed by code defined in
an anonymous class.  Code in an anonymous class or a local class
cannot access non-final local variables.)

Register an ActionListener

Listing 6 shows an anonymous class used to instantiate and register an ActionListener
object on the button.

    startButton.addActionListener(
new ActionListener(){
public void actionPerformed(
ActionEvent e){
try{
//The communication process is now
// in the TRANSACTION state.
//Retrive and save messages
if(numberMsgs == 0){
outputStream.println("STAT");
String stat = validateOneLine();
//Get the number of messages as
// a String.
String numberMsgsStr =
stat.substring(
4,stat.indexOf(" ",5));
//Convert the String to an int.
numberMsgs = Integer.parseInt(
numberMsgsStr);
}//end if numberMsgs == 0
//NOTE: Msg numbers begin with 1,
// not 0.
//Retrieve and save each
// message. Each msg ends with a
// period on a new line.
msgNumber = msgCounter + 1;
if(msgNumber <= numberMsgs){
//Process the next message.

//Get and save a unique identifier
// for the message from the server
// and validate the response.
outputStream.println(
"UIDL " + msgNumber);
uidl = validateOneLine();

//Open an output file to save
// the message. Use the UIDL
// as the file name.
pathFileName = dataPath + uidl;
DataOutputStream dataOut =
new DataOutputStream(
new FileOutputStream(
pathFileName));

//Send a RETR command to begin
// the message retrieval process
outputStream.println(
"RETR " + msgNumber);
//Validate the response.
String retrResponse =
validateOneLine();

//Read the first line in the
// message from the server.
String msgLine =
inputStream.readLine();

//Continue reading lines until
// a "." is encountered as the
// first char in a line. That
// signals the end of the msg.
while(!(msgLine.equals("."))){
//Write the line to the output
// file and read the next
// line. Insert newline
// characters when writing the
// output to the file.
dataOut.writeBytes(
msgLine + "n");
msgLine = inputStream.readLine();

}//end while
//Close the output file. The
// message is now stored in a
// local file with a file name
// based on the unique ID
// provided by the server.
dataOut.close();

//Show progress
textArea.append(msgNumber + "n");

//Increment the message counter
// in preparation for
// processing the next message.
msgCounter++;

//Disable this statement to require
// the user to press the button
// for each new message.
Toolkit.getDefaultToolkit().
getSystemEventQueue().
postEvent(new ActionEvent(
startButton,
ActionEvent.
ACTION_PERFORMED,
"Start/Next"));

}//end if msgNumber <= numberMsgs
else{//msgNumber > numberMsgs
//No more messages. Disable the
//Start/Next button.
startButton.setEnabled(false);

//Alert the user
Toolkit.getDefaultToolkit().beep();
Thread.currentThread().sleep(300);
Toolkit.getDefaultToolkit().beep();
Thread.currentThread().sleep(300);
Toolkit.getDefaultToolkit().beep();
}//end else
}//end try
catch(Exception ex){
ex.printStackTrace();}
}//end actionPerformed
}//end ActionListener
);//end addActionListener

Listing 6

The purpose of the ActionListener object is to download all the
messages on the public email server, put each separate message in a
different file, and store those files in the working folder.

While the code in Listing 6 is not identical to the code that I
explained in the earlier
lesson, it is very similar, and some less complex. 
Therefore, if you understood the code in that lesson, you should have
no difficulty understanding the code in Listing 6.

Configure the GUI

Listing 7 configures the GUI by placing the various components in the Frame,
setting the size of the Frame, and making it visible.

    add(startButton);
add(textArea);
textArea.setText("");
setLayout(new FlowLayout());

setTitle("Copyright 2004, R.G.Baldwin");
setBounds(274,0,400,400);
//Make the GUI visible.
setVisible(true);
}//end constructor

Listing 7

Listing 7 also signals the end of the constructor for the class named VirPro01a.

The validateOneLine method

The complete program, shown in Listing 40 near the end of the lesson
also contains a utility method named validateOneLine
This method is identical to the method having the same name that I
explained in the earlier
lesson.  Therefore, I won’t bore you by repeating that
explanation here.

And that is the end of the class named VirPro01a.  As you
can see, it doesn’t contain much new material relative to my earlier
lessons.

Program VirPro01b

That brings us to the program named VirPro01b,
which contains a great deal of new information relative to my earlier
lessons.

As described earlier, this is one of a pair of programs designed to be
used together, in conjunction with a good virus scanner program to
prevent messages with viruses from contaminating an email inbox.

This program should be run after the program named VirPro01a
has been run, and a virus checker has been used to confirm that the
files produced by VirPro01a are free of viruses, or that any
such
files containing viruses have been removed from the working folder (hopefully
either deleted or quarantined).

This program forwards email messages to a secret email account. 
On my system, which is relatively slow, using a cable modem, about
three
to four seconds are
required to forward each message.

This program was tested using SDK 1.4.2 under WinXP.

The forwardEmailMsg method

I’m going to begin by explaining what may be the most complex part of
this program.  That is a method named forwardEmailMsg
Part of what makes this
method complex is that it depends on the largely undocumented Sun class
named sun.net.smtp.SmtpClient.

This is a very useful class for sending email messages, and it is
included in SDK version 1.4.2.  However, it doesn’t seem to be
included in the SDK 1.4.2 documentation, and it is very
difficult to find much in the way of documentation for this class.

Where is the documentation?

About the best that I have been able to find in the way of
documentation can
be found at http://swig.stanford.edu/pub/java/javadoc/sun/net/smtp/SmtpClient.html.

The operational description of the class on the above web site reads as
follows:


This class implements the SMTP client. You can send a piece of mail by
creating a new SmtpClient, calling the “to” method to add destinations,
calling “from” to name the sender, calling startMessage to return a
stream to which you write the message (with RFC733 headers) and then
you finally close the Smtp Client.”

Very little descriptive information

Other than the brief description given above, the documentation
provides very
little in the way of descriptive information.  However, it does
provide such crucial information as method names, parameter types,
return types, etc.

Many cautions

If you search the Web for information about this class, you will find
numerous cautions about using it, most of which are based on the fact
that it is
reportedly not supported by Sun.  If you are working on a product
that will require long-term support and maintenance,
that is probably a genuine concern.  However, I wrote this program
largely for my own use in protecting myself against email
viruses.  Therefore, I’m not concerned about long-term support and
maintenance.  The class is easy to use, and therefore seems to
be a pretty good choice for this purpose.

The readLines method

The forwardEmailMsg method uses another
method named readLines.  Therefore, I will explain the readLines
method before embarking on an explanation of the forwardEmailMsg
method.

The readLines method begins in Listing 8.

  private String readLines(String pathFileName,
String firstLine,String lastLine){

Listing 8

The method parameters

This method receives the following three String parameters.

  • pathFileName
  • firstLine
  • lastLine

Basically, the method is designed to allow you to extract consecutive
lines of text from a text file based on the starting content of the
first line and the last line that you want to capture.

The readLines method reads and saves lines of text from a text
file starting with the line that starts with firstLine
and ending with the line that starts with lastLine.

If lastLine is null, data is saved to the end of the
file.

If firstLine is null, data is saved beginning with the
first line in the file.

The lines of text from the file are saved by concatenating them into a
single String object with a newline inserted into the
string at the end of each line.

The name and path to the file is given by pathFileName.

The remainder of the method

The remaining code in the readLines method is shown in Listing
9.

    StringBuffer strBuf = new StringBuffer();
try{
BufferedReader inDataMsg
= new BufferedReader(new FileReader(
pathFileName));

String data;
boolean isSave = false;
while((data = inDataMsg.readLine())
!= null){

if(((firstLine == null) ||
(data.startsWith(firstLine))) &&
(isSave == false)){
isSave = true;
}//end if

if(isSave){
strBuf.append(data + "n");
}//end if

if((lastLine != null) &&
(data.startsWith(lastLine))){
break;//no need to read any more
}//end if

}//end while loop
inDataMsg.close();//Close file
}catch(Exception e){e.printStackTrace();}
return new String(strBuf);
}//end readLines

Listing 9

Don’t confuse readLines with readLine

Don’t confuse the name of this method with the readLine method
of the BufferedReader class used internally in Listing 9.

Otherwise, the code in Listing 9 is very straightforward and shouldn’t
require further explanation.

The format of a message

While I designed the readLines method to be fairly general in
nature, I did
design it with the task of extracting text lines from raw email
messages in mind, so it may be useful for you to see an example of such
a message.

Figure 3 shows the raw text for a very simple email message that I sent
to myself for purposes of illustration.  (Note
that I had to manually insert a large number of line breaks in the text
to force it to fit into this narrow publication format.)

Return-Path: <Baldwin@DickBaldwin.com>
Received: from ms-smtp-01-eri0.texas.rr.com
(ms-smtp-01.texas.rr.com [24.93.47.40])
by omnistarhost.com (8.11.6/8.11.6)
with ESMTP id i1G1PeX29829
for <baldwin@dickbaldwin.com>; Sun,
15 Feb 2004 19:25:40 -0600
Received: from DickBaldwin.com
(cs24339-166.austin.rr.com [24.243.39.166])
by ms-smtp-01-eri0.texas.rr.com
(8.12.10/8.12.7) with ESMTP id i1G1JHLc003760
for <baldwin@dickbaldwin.com>;
Sun, 15 Feb 2004 19:19:20 -0600 (CST)
Message-ID: <40301A94.6070504@DickBaldwin.com>
Date: Sun, 15 Feb 2004 19:19:16 -0600
From: Richard Baldwin <Baldwin@DickBaldwin.com>
Reply-To: Baldwin@DickBaldwin.com
User-Agent: Mozilla/5.0 (Windows; U; Windows
NT 5.1; en-US; rv:1.4) Gecko/20030624
Netscape/7.1 (ax)
X-Accept-Language: en-us, en
MIME-Version: 1.0
To: baldwin@dickbaldwin.com
Subject: A test msg to illustrate message
 structure

Content-Type: text/plain;
charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit
X-MailScanner-Information: Please
contact the ISP for more information
X-MailScanner: Found to be clean
Status:

This is a test message.
--
Richard G. Baldwin (Dick Baldwin)
Home of Baldwin's on-line Java Tutorials
http://www.DickBaldwin.com

Professor of Computer Information Technology
Austin Community College
(512) 223-4758 or (512) 250-8682
mailto:Baldwin@DickBaldwin.com
http://www.austincc.edu/baldwin/

Figure 3

I highlighted two lines in red in Figure 3.  (The Subject:
line was originally a single line before I manually inserted a line
break after the word message.)

The Subject line and the Status line

The Subject line will figure prominently in the code in the forwardEmailMsg
method.  In this version of the program, the message number is
inserted into the Subject line before the message is forwarded
to the secret email account.

The Status line and everything above it is
considered to be the message header.  Everything below the Status
line is considered to be the message body.  If you find yourself
reading the raw text of an email message with a text editor, you will
usually want to concentrate on the body of the message.

Back to the forwardEmailMsg method

The forwardEmailMsg method begins in Listing 10.

  private boolean forwardEmailMsg(
String recipient,
String smtpServer,
String tag,
String pathFileName){

Listing 10

The incoming parameters to the forwardEmailMsg method are:

  • recipient – Email address of the person that is to
    receive
    the message.  In this case, it is the email address of the secret
    email account.  This is one of the
    command line parameters when the program is started.
  • smtpServer – Identification of an smtp server on which
    you are authorized to send email messages.  This is one of the
    command line parameters when the program is started.
  • tag – A string that is inserted at the beginning of the
    Subject before forwarding the message to the recipient.  I used
    the original message number from the public email server, but you could
    change this to something else if you choose.
  • pathFileName – Identification of the message that will be
    forwarded to the secret email account.

Local variable

Listing 11 declares and initializes a local variable that
will be used later in the method.

      StringBuffer message = new StringBuffer(
"No message found");

Listing 11

The SmtpClient constructor

The documentation identified earlier
identifies two overloaded constructors for the class:

  • One that creates an uninitialized SMTP client.
  • One that creates a new SMTP client connected to a specified host.
      try{
SmtpClient smtp =
new SmtpClient(smtpServer);

Listing 12

The code in Listing 12 uses the second constructor described above to
instantiate an SmtpClient object connected to the smtpServer
provided by the user as a command line parameter when the program was
started.

The from() method

Although the documentation identified earlier
doesn’t provide a description of this method, it can be deduced
intuitively (and from the brief description at the beginning of the
documentation)
that this method needs to receive the email address
of the
sender of the message.

        smtp.from(recipient);

Listing 13

A possible email address is required

In this case, it really doesn’t matter who the email message is sent
by, provided that it is a possible email address.  The email
client program at the secret email account will interpret
the From: line in the header of the message as the sender of
the message.

(It is necessary, however, that the email address passed
to the from method be a
possible email address.  Otherwise, the from method will
throw an exception.

You might reasonably ask why the from method exists if it
doesn’t matter.  The email address that you pass to the from
method will show up in the line that begins with Return-Path in the
message header.  See Figure 3 for an example.
)

Since we know that the recipient’s email address is a possible email
address, the recipient’s email address was passed to the from
method as the sender of the message.

(If the header doesn’t contain a From: line,
which happens occasionally, the SmtpClient object will
construct a new header showing the recipient as the sender, and insert
this new header at the beginning of the message.)

The to() method

This method needs to receive the email address of the
intended recipient of the message.

The code in Listing 14 passes the recipient’s email address to the to()
method.

        smtp.to(recipient);

Listing 14

The startMessage method

This method
gets and returns a PrintStream object that is used to construct
the message.

        PrintStream msg = smtp.startMessage();

Listing 15

Listing 15 invokes the startMessage method to get a reference
to the PrintStream
object and to save the reference in a reference variable named msg.

Get the entire message as a String

The code in listing 16 invokes the readLines method to get the
entire message from the file pointed to by pathFileName and to
convert that message to a single String object.

        message = new StringBuffer(readLines(
pathFileName,null,null));

Listing 16

Note that the code in Listing 16 passes two null parameters to the readLines
method, instructing it to use all text lines contained in the file to
construct and return a String.

(See Figure 3 for an example of the contents of a raw
message file.)

Insert the tag into the Subject line

The code in Listing 17 performs the following actions:

  • Inserts the value of the incoming parameter tag into the
    Subject line immediately before the current body of the subject.
  • Converts the message from a StringBuffer object to a String
    object and invokes the println method to insert it into the
    output stream.
        message = message.insert(message.indexOf(
"Subject: ")+9,tag);

msg.println(new String(message));

Listing 17

Send
the message

The code in Listing 18 invokes the closeServer method on the SmtpClient
object. 

        //Close the stream and send the message
smtp.closeServer();

return true;
}catch( Exception e ){
System.out.println("n" + e);
System.out.println("Forwarding email");
Toolkit.getDefaultToolkit().beep();
try{
Thread.currentThread().sleep(300);
}catch(Exception ex){
System.out.println(ex);
}//end catch
Toolkit.getDefaultToolkit().beep();
return false;
}//end catch

}//end forwardEmailMsg

Listing 18

This causes the message to be
sent to the recipient, although that isn’t explicitly indicated by the
documentation discussed earlier.

Return true on success

Then the code in Listing 18 returns true.  This notifies the
calling method that the message has been sent, and that it is all right
to
delete the message from the server and to move it from the working
folder to the archive folder.

Exceptions

If any of the SmtpClient methods called in the forwardEmailMsg
method throws an exception, it will be caught by the catch block in
Listing 18.

This catch block prints some diagnostic information, beeps to alert the
user, and then returns false.  This is a signal to the calling
method that the message has not been forwarded, should not be deleted
from the public email server, and should not be moved to the archive
folder.

Depending on the exact nature of the exception, it may or may not be
useful to rerun VirPro01b in an attempt to send the
message.  If the cause of the exception was a simple timeout due
to heavy network traffic (or due to the SMTP server going down),
there is a good chance that a rerun will be
successful in sending the message the second time around.  For
other more serious problems (such as a seriously malformed message),
a rerun might not be successful and the
message file becomes a candidate for examination with a text editor as
described earlier.

The moveFile method

There is one other utility method that I will discuss before getting
into the details of the constructor for the VirPro01b
class.  Listing 19 shows the moveFile method in its
entirety.

  private void moveFile(String pathFileName,
String archivePath){
String fileName = pathFileName.substring(
pathFileName.lastIndexOf('/') + 1);
String archivePathFileName =
archivePath + fileName;

boolean moved =
new File(pathFileName).renameTo(
new File(archivePathFileName));

if(!moved)System.out.println(
"Unable to move " + new File(pathFileName)
+ "nto " + new File(archivePathFileName));
}//end moveFile method

Listing 19

Incoming parameters

This method receives two incoming parameters:

  • pathFileName – Current location and name of a file.
  • archivePath – Destination of the file.

This is the method that is used to move the message files from the
working folder to the archive folder.  It moves a file from its
current location, specified by pathFileName, to a new location
specified by archivePath.

The reName method of the File class returns true if the
operation is successful and false otherwise.  For example, if a
file with the same name already exists in the destination folder, the
operation will fail, and the code in Listing 19 will print a message
indicating that the move was not successful.

The VirPro01b class

Listing 20 shows the beginning of the VirPro01b class along
with the declaration and initialization of some instance
variables.  The comments in Listing 20 explain the use of the
instance variables, so I won’t elaborate further on them.

class VirPro01b extends Frame{
//The following is the ID of the secret email
// account provided as a command line parameter
// when the program is started.
String recipient;

//The following is the local folder where
// message files are stored awaiting virus
// scanning and forwarding. You may want to
// change this to a different folder.
String dataPath = "./Messages/";

//The following is the local folder where the
// messages are stored after they have been
// scanned and forwarded to the secret email
// account. They are automatically moved to
// this folder after being deleted from the
// public email server. You will want to
// manually empty this folder periodically.
String archivePath = "./Archives/";

//The following are working variables used by
// the program for various purposes.
BufferedReader inputStream;
PrintWriter outputStream;
Socket socket;
String pathFileName;
Vector msgToDelete = new Vector();


Listing 20

I will mention, however, that you can change the location and names of
the working folder and archive folder simply by modifying the
initialization values for dataPath and archivePath in
Listing 20.

(Be sure to create the new folders that you specify
before attempting to run the program.)

The main method

The main method is shown in its entirety in Listing 21.

  public static void main(String[] args){
if(args.length != 5){
System.out.println("Usage: java VirPro01b "
+ "pubServer userName password "
+ "secretServer smtpServer");
System.exit(0);
}//end if

new VirPro01b(args[0],args[1],args[2],
args[3],args[4]);
}//end main

Listing 21

The code in the main method confirms the correct number of
command line parameters, and then uses those parameters to instantiate
an object of the VirPro01b class.

The constructor for the VirPro01b class

The constructor begins by storing the secret email address in the
instance variable named recipient to make it available to other
methods of the class.  The other incoming parameters are used only
within the constructor, so there is no need to save them as instance
variables.

  VirPro01b(final String server,
final String userName,
final String password,
String secretServer,
final String smtpServer){

recipient = secretServer;

Listing 22

It is necessary, however, to declare them final because they
will be accessed by code in an anonymous class definition.

A WindowListener object

The code in Listing 23 defines an anonymous class, and instantiates an
anonymous object of that class that implements the WindowListener
interface.

    this.addWindowListener(
new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}//end windowClosing
}//end WindowAdapter()
);//end addWindowListener

Listing 23

This WindowListener object is registered on the Frame
to cause the program to terminate when the user presses the close
button in the upper right corner of the Frame.

The GUI components

The code in Listing 24 instantiates the two buttons and the text area
shown in the user interface in Figure 2.

    final Button startButton =
new Button("Start");
final Button deleteButton = new Button(
"Delete Msg On Server");
final TextArea textArea =
new TextArea(20,50);

Listing 24

Again, the references must be declared final because they will
be accessed from within an object of an anonymous class.

An ActionListener on the Start button

When you start the program running, everything gets initialized,
following which the
program waits for the user to press the Start button. 
When the user presses the Start button, the program starts
forwarding email messages to the secret email account.

The code in Listing 25 instantiates and registers an ActionListener
on the Start button to handle the forwarding of
messages.  Listing 25 shows the beginning of the actionPerformed
method,
which is executed when the user presses the Start button.

    startButton.addActionListener(
new ActionListener(){
public void actionPerformed(
ActionEvent e){
startButton.setEnabled(false);

File dataDir = new File(dataPath);
String[] dirList = dataDir.list();

Listing 25

Listing 25 immediately disables the Start button to ensure that
it can be pressed only once.

The objective

The objective of the actionPerformed method is to convert all
of the message files in the working folder into email message format
and to send them to the secret email account.

Listing 25 starts this process by getting a directory listing of all
the files in the working folder.

(This code assumes that the working folder contains only
message files.  If you store other files in that folder, such as a
ReadMe.txt file, you will need to add a filter to the code in
Listing 25 to isolate the message files in the directory listing.)

Process all the files in the working folder

Listing 26 shows the beginning of a for loop designed to
extract each message file from the working folder, convert it to email
message format, and send
it to the secret email account.

          for(int msgCounter = 0;
msgCounter < dirList.length;
msgCounter++){
String fileName =
dirList[msgCounter];
pathFileName = dataPath + fileName;

Listing 26

There is nothing complicated about the code in Listing 26, so it should
not require an explanation.

Get the message number

My code tags the subject of each forwarded message with the original
message number of the message as it appeared on the public email server.

(As I mentioned earlier, you can create and use some
other tag if you choose to do so.  The only requirement is that
the tag must be expressed as a String.)

A three-digit string

The
code in Listing 27 gets the original message number and formats it as a
three-digit string.

            String strMsgNumber =
pathFileName.substring(
pathFileName.indexOf(" "),
pathFileName.lastIndexOf(" ")).
trim();
int msgNumber = Integer.parseInt(
strMsgNumber);
String msgNumberStr;
if(msgNumber < 10){
msgNumberStr = "00" + msgNumber;
}else if(msgNumber > 99){
msgNumberStr = "" + msgNumber;
}else{
msgNumberStr = "0" + msgNumber;
}//end else

Listing 27

Information on file names

In order for you to understand the code in Listing 27, I need to give
you some background information on the file names
assigned to each message when it is written into a file by the program
named VirPro01a.

Here is a typical file name for a message file in the working folder:

+OK 38 402fb6da00000098

How is the file name constructed?

This file name is constructed directly from a response received from
the email
server after sending a UIDL command to the server. 
I believe that this is a standard response for all POP3 email
servers.

The first three characters, +OK, indicate that the
command was accepted.

(If the command had not been accepted, the response
would have begun with -ERR.  For more information on this,
see the method named validateOneLine in Listing 41 near the end
of the lesson.)

The message number

The characters between the two spaces specify the message number
assigned to this message on the public email server at the time the
command
was received.

(As I understand it, this message number will change if
messages with lower message numbers are deleted from the server. 
In other words, any time you access the server to download messages:

  • The message numbers will begin with 1.
  • Messages will have sequential
    numbers.
  • There will be no gaps between the sequential message
    numbers.

If you go back and re-examine the code in VirPro01a,
you will see that I downloaded all the messages without deleting any
messages.  Had that program needed to delete messages, I would
have downloaded all the messages before deleting any messages to avoid
having duplicate message numbers.)

The unique identifier, UIDL

The long string of characters following the second space in the file
name is a unique ID
assigned by the server to that message.

(Again, as I understand it, this unique ID will never be
duplicated for another message on that server for the same email
account, but may be duplicated for messages on different email accounts
on the same server, or may be duplicated by different email servers.)

The pathFileName variable

The value of pathFileName in Listing 27 is simply the
file name prepended by the path to the file.

Given the form of pathFileName, you should be able to
understand how the code in Listing 27 extracts the message number and
converts it into a three-digit string containing the message number,
such as 001, 063, or 169.

(If I ever have more than 999 messages on the server at
any one time, I will have to expand this code to produce a four-digit
message number string.  I will call that an M1K problem in
deference to the Y2K problems of several years ago.)

Forward the message to the secret email account

The code in Listing 28 invokes the forwardEmailMsg method (discussed
earlier)
to format the information in the message file into an
email message and to send it to the secret email account.

            boolean okToDelete =
forwardEmailMsg(
recipient,
smtpServer,
"{"+ msgNumberStr +"}",
pathFileName);

Listing 28

Recall that the forwardEmailMsg method returns true if the
forwarding operation is successful, and returns false otherwise. 
The return value is saved in the variable named okToDelete in
Listing 28.

Mark the message for deletion

If the forwardEmailMsg method returns true, the code in Listing
29 adds the pathFileName identifying the message file to a Vector
collection referred to by msgToDelete.  The contents of
this collection will be used later to delete the message from the
public email server, and also to move the message file from the working
folder to the archive folder.

            if(okToDelete){
textArea.append("Forwarded " +
msgNumberStr + "n");
msgToDelete.add(pathFileName);
}else{
textArea.append("Failed " +
msgNumberStr + "n");
}//end else
}//end for loop on directory length

Listing 29

Don’t mark the message for deletion

If the forwardEmailMsg method returns false, the pathFileName
for that message file is not added to the collection.  As a
result, the message will not be deleted from the email server, and the
message file will not be moved to the archive folder.

(If you don’t have WebMail access to your POP3 server,
you may need to write a special short program whose purpose is to
delete a single malformed message that never gets forwarded, and
therefore never gets deleted from the server.)

Display information for the user

The code in Listing 29 also displays information in the text area of
Figure 2 to keep the user informed as to the success or failure of the
attempt to forward the message to the secret email account.

End of the loop

The code in Listing 29 also signals the end of the for loop
that controls the processing of all the message files in the working
folder.

Enable the Delete button

The code in Listing 30 enables the Delete button and posts a
deletion message in the text area of Figure 2.

          deleteButton.setEnabled(true);
textArea.append("nDo you want to "
+ "delete messages from server?n");

Listing 30

Enabling the Delete button makes it possible for the user to
activate the ActionListener registered on that button (still
to be discussed)
to delete messages from the public email server,
and to move message files from the working folder to the archive folder.

Alert the user

The code in Listing 31 produces three audible beeps to alert the user
that the message forwarding process is complete, and that it is time to
make
a decision regarding the deletion of messages from the public email
server.

          try{
Toolkit.getDefaultToolkit().beep();
Thread.currentThread().sleep(300);
Toolkit.getDefaultToolkit().beep();
Thread.currentThread().sleep(300);
Toolkit.getDefaultToolkit().beep();
}catch(Exception ex){
ex.printStackTrace();
}//end catch
}//end actionPerformed
}//end ActionListener
);//end addActionListener

Listing 31

The code in Listing 31 also signals the end of instantiation of the ActionListener
that is registered on the Start button in Figure 2.

An ActionListener on the Delete button

Listing 32 shows the beginning of code that instantiates and registers
an ActionListener object on the Delete button shown in
Figure 2.

    deleteButton.addActionListener(
new ActionListener(){
public void actionPerformed(
ActionEvent e){
deleteButton.setEnabled(false);
textArea.append("n");

Listing 32

The code in Listing 32 immediately disables the Delete button
to ensure that this code can be activated only once.

The code in Listing 32 also moves the selection point in the text area
to the end of its contents in case it has been scrolled to another
point.

Connect to the public email server

The code in Listing 33 gets a connection to the public email server in
preparation for deleting messages from the server.

          int port = 110; //pop3 mail port
try{
//Get a socket, connected to the
// specified server on the specified
// port.
socket = new Socket(server,port);

//Get an input stream from the socket
inputStream = new BufferedReader(
new InputStreamReader(
socket.getInputStream()));

//Get an output stream to the socket.
outputStream = new PrintWriter(
new OutputStreamWriter(
socket.getOutputStream()),
true);

//Display the msg received from the
// server on the command line screen
// immediately following connection.
String connectMsg =
validateOneLine();
System.out.println(
"Connected to server "
+ connectMsg);

//The communication process is now in
// the AUTHORIZATION state. Send the
// user name and password to the
// server.
//Commands are sent in plain text,
// upper case to the server. Some
// commands require an argument
// following the command, as is the
// case with USER.
//Send the command.
outputStream.println("USER "
+ userName);
//Get response and confirm that the
// response was +OK and was not -ERR.
String userResponse =
validateOneLine();
//Display the response on the
// command line screen.
System.out.println("USER "
+ userResponse);
//Send the password to the server
outputStream.println("PASS "
+ password);
//Validate the server's response as
// +OK. Display the response in the
// process.
System.out.println("PASS "
+ validateOneLine());
}catch(Exception ex){
ex.printStackTrace();
}//end catch

Listing 33

The code in Listing 33 is essentially the same as corresponding code
discussed in conjunction with the program named VirPro01a, so I
won’t discuss it further.

Begin the message deletion process

The code in Listing 34 begins the process of:

  • Extracting message
    identification information from the Vector collection referred
    to by msgToDelete.
  • Deleting the identified messages from the public
    email server.
  • Moving the corresponding message files from the
    working folder to the archive folder.
          for(int cnt = 0;
cnt < msgToDelete.size();cnt++){
pathFileName =
(String)msgToDelete.elementAt(cnt);
String strMsgNumber =
pathFileName.substring(
pathFileName.indexOf(" "),
pathFileName.lastIndexOf(" ")).
trim();
int msgNumber = Integer.parseInt(
strMsgNumber);

Listing 34

The message number is required

In order to delete a message from the email server, that message must
be identified by the message number on the server.  The code in
Listing 34 extracts the message number from the identification
information stored in the Vector collection.

This code is essentially the same as code that I discussed in
conjunction with Listing 27, so I won’t discuss it further. 
Suffice it to say that this code is executed once for each message
identifier contained in the Vector collection referred to by msgToDelete.

How to delete a message from the server

Deletion of a message from the server is accomplished by marking the
message for deletion by issuing a DELE command while in the TRANSACTION
state.  The message is actually deleted later when the client
issues a QUIT command
to the server causing the server to enter the UPDATE state.  If
the program aborts prematurely before sending a QUIT command, marked
messages are not deleted from the server.

Mark the message for deletion

The message is marked for deletion by executing the statement shown in
red in Listing 35.

System.out.println(
"Deletion is temporarily disabled.");
/*Deletion from the server temporarily disabled.
*Do not enable this deletion code until you
* understand exactly what you are doing.

outputStream.println(
"DELE " + msgNumber);

//Validate the response and display
// it on the GUI.
textArea.append(
"Msg: " + msgNumber + " "
+ validateOneLine()+"n");
textArea.append(
"Marked:" + msgNumber + "n");

*/


Listing 35

As indicated by the comments, this statement has been disabled in the
listing of the program given in Listing 41 near the end of the
lesson. 

CAUTION

You should not enable this statement until you have
thoroughly tested the program on your system and are satisfied that it
is behaving properly.  If you do, you may lose email messages by
deleting them without properly forwarding them to your secret email
account.

As long as you don’t delete the messages from the server, you can read
them using your normal message-reading procedure with your normal email
client program.

(If your public email server provides the ability for
you to view the messages on the server using WebMail, this is a useful
way to monitor everything that is going on while you are testing this
program on your system.  WebMail normally allows you to view the
messages without removing them from the server.)

Move the message file

Listing 36 invokes the moveFile method (discussed earlier)
to move the message file from the working folder to the archive folder.

            moveFile(pathFileName,archivePath);

}//end for loop on msgToDelete.size()

Listing 36

Listing 36 also signals the end of the loop that controls the deletion
and moving of all the messages identified by the contents of the Vector
collection referred to by msgToDelete.

Terminate the session

Listing 37 terminates the session with the public email server by
issuing a QUIT command, causing previously marked messages to be
deleted from the server.

          outputStream.println("QUIT");
String quitResponse =
validateOneLine();
System.out.println(
"QUIT " + quitResponse);

Listing 37

Assuming that the response to the QUIT command is +OK,
the server goes into the UPDATE mode and deletes the messages.  If
the response is -ERR for some reason, the server does
not go into the UPDATE mode, and the messages will not be deleted.

Close the socket and clean things up

The code in Listing 38 closes the socket and displays an informative
message on the text area in Figure 2.

          try{
socket.close();
}catch(Exception ex){
ex.printStackTrace();
}//end catch

textArea.append("nnMessages deleted "
+ "from server.n");
}//end actionPerformed
}//end ActionListener
);//end addActionListener

Listing 38

Listing 38 also signals the end of the instantiation of the ActionListener
object registered on the Delete button.

Configure the GUI

Listing 39 configures the GUI shown in Figure 2 by placing various
components on it, setting the size, and making it visible.

    add(startButton);
add(deleteButton);
deleteButton.setEnabled(false);
add(textArea);
textArea.setText("");
setLayout(new FlowLayout());

setTitle("Copyright 2004, R.G.Baldwin");
setBounds(274,0,400,400);
//Make the GUI visible.
setVisible(true);
}//end constructor
}//end class VirPro01b

Listing 39

Listing 39 also signals the end of the constructor and the end of the
class for the class named VirPro01b.

The method named validateOneLine

The complete program, shown in Listing 41 near the end of the lesson,
also contains a utility method named validateOneLine
This method is identical to the method having the same name that I
explained in an earlier
lesson.  I won’t repeat that explanation here.

Run the Programs

I encourage you to copy the code from Listing 40 and Listing 41
below.  Compile and run the programs.  Experiment with them,
improving them as you see fit relative to your specific situation.

(IMPORTANT:  Let me caution you again not to
enable
the
DELE
code in the program named VirPro01b until you are certain
that you actually want to delete messages from the server.  Once a
message is deleted from the server, there is no way to recover it from
the server.)

Summary

This lesson provides two Java programs and an operational scheme for
preventing email viruses from contaminating your email data.

Preventing email data contamination can be very important, not only
with respect to protecting valuable email correspondence, but also with
respect to ensuring that you have backups suitable for restoring your
disk data when needed.

What’s Next?

I have no immediate plans for publishing additional lessons on this
topic.  However, my long-term plans call for combining this scheme
with a SPAM screening scheme for the purpose of protecting against
email viruses and blocking SPAM with the same program.

Complete Program Listing


Complete listings of the programs are provided in Listing 40 and
Listing 41.

Disclaimer of responsibility:  If you elect to use these
programs
you use them at your own risk.  Make absolutely certain that you
understand what you are doing before you compile and execute the
programs. 
Inappropriate use could result in the loss of email messages.  The
author of these programs, Richard G. Baldwin, accepts no responsibility
for any losses that you may incur as a result of using these programs.

/*File VirPro01a.java Copyright 2004, R.G.Baldwin
Rev 02/14/04

This is one of a pair of programs designed to be
used together, in conjunction with a good virus
scanner program to prevent messages with viruses
from contaminating an email inbox.

This program should be run before the program
named VirPro01b. This program downloads all the
messages on a POP3 email server and stores each
message in a separate file in a folder on the
local disk.

After this program has been run, the user should
use a good virus checker to scan all of those
files, removing any that contain a virus.

After scanning the files, the user should run
VirPro01b to forward the remaining clean messages
to a secret email account. After forwarding the
messages to the secret email account, the user is
given an opportunity to delete those messages
from the public email server, and to move the
local files containing the messages from the
working directory into an archive directory.

If there is any question as to whether a
message was successfully forwarded, (as might
be the case under bad network conditions), that
message is not deleted from the server and is not
moved to the archive folder. It remains in the
working folder so that the user can run
VirPro01b again in an attempt to successfully
forward that message and any other messages
remaining in the working folder to the secret
email account.

On my system with a cable modem, about three to
four seconds are required to forward each message
to my secret email account.

This program is designed to be used with a POP3
email server. For technical information on POP3,
see RFC 1725 at
http://www.cis.ohio-state.edu/htbin/rfc/rfc1725.
html

A POP3 Command Summary follows based on the
information at that web site.

Minimal POP3 Commands:
USER name
PASS string
QUIT
STAT
LIST [msg]
RETR msg
DELE msg
NOOP
RSET
QUIT

Optional POP3 Commands:
APOP name digest
TOP msg n
UIDL [msg]

POP3 Replies:
+OK
-ERR

Usage: Enter the following at the command line:
java VirPro01a pubServer userName password

where the command line parameters identify your
public email server, your user name, and your
password on that email server.

Tested using SDK 1.4.2 under WinXP
************************************************/

import java.net.*;
import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;

class VirPro01a extends Frame{
//The following is the local working folder
// where message files are stored awaiting
// scanning and forwarding. You might want to
// use a different folder for this purpose. If
// so, simply provide the path and folder name
// as a string.
String dataPath = "./Messages/";

//The following are working variables used by
// the program for various purposes.
int numberMsgs = 0;
int msgCounter = 0;
int msgNumber;
String uidl = "";//unique POP3 msg ID
BufferedReader inputStream;
PrintWriter outputStream;
Socket socket;
String pathFileName;

public static void main(String[] args){
if(args.length != 3){
System.out.println("Usage: java VirPro01a "
+ "pubServer userName password");
System.exit(0);
}//end if

new VirPro01a(args[0],args[1],args[2]);
}//end main
//===========================================//

//Constructor
VirPro01a(String server,String userName,
String password){
int port = 110; //pop3 mail port
try{
//Get a socket, connected to the
// specified server on the specified
// port.
socket = new Socket(server,port);

//Get an input stream from the socket
inputStream = new BufferedReader(
new InputStreamReader(
socket.getInputStream()));

//Get an output stream to the socket.
// Note that this stream will autoflush.
outputStream = new PrintWriter(
new OutputStreamWriter(
socket.getOutputStream()),true);

//Display the msg received from the
// server on the command line screen
// immediately following connection.
String connectMsg = validateOneLine();
System.out.println("Connected to server "
+ connectMsg);

//The communication process is now in the
// AUTHORIZATION state. Send the user
// name and password to the server.
//Commands are sent in plain text, upper
// case to the server. Some commands
// require an argument following the
// command, as is the case with USER.
//Send the command.
outputStream.println("USER " + userName);
//Get response and confirm that the
// response was +OK and was not -ERR.
String userResponse = validateOneLine();
//Display the response on the command-
// line screen.
System.out.println("USER " + userResponse);
//Send the password to the server
outputStream.println("PASS " + password);
//Validate the server's response as +OK.
// Display the response in the process.
System.out.println(
"PASS " + validateOneLine());
}catch(Exception e){e.printStackTrace();}

//Register a window listener to service
// the close button on the Frame.
this.addWindowListener(
new WindowAdapter(){
public void windowClosing(WindowEvent e){

//Terminate the session with the
// server.
outputStream.println("QUIT");
String quitResponse =
validateOneLine();
//Display the response on the
// command line screen.
System.out.println(
"QUIT " + quitResponse);

try{
socket.close();
}catch(Exception ex){
System.out.println("n" + ex);}

System.exit(0);
}//end windowClosing
}//end WindowAdapter()
);//end addWindowListener

//Note that the compiler requires the
// reference to the following components to
// be final because they are accessed from
// within an anonymous class definition.
final Button startButton =
new Button("Start");
final TextArea textArea =
new TextArea(20,50);

//Register an ActionListener on the
// startButton.
startButton.addActionListener(
new ActionListener(){
public void actionPerformed(
ActionEvent e){
try{
//The communication process is now
// in the TRANSACTION state.
//Retrive and save messages
if(numberMsgs == 0){
outputStream.println("STAT");
String stat = validateOneLine();
//Get the number of messages as
// a String.
String numberMsgsStr =
stat.substring(
4,stat.indexOf(" ",5));
//Convert the String to an int.
numberMsgs = Integer.parseInt(
numberMsgsStr);
}//end if numberMsgs == 0
//NOTE: Msg numbers begin with 1,
// not 0.
//Retrieve and save each
// message. Each msg ends with a
// period on a new line.
msgNumber = msgCounter + 1;
if(msgNumber <= numberMsgs){
//Process the next message.

//Get and save a unique identifier
// for the message from the server
// and validate the response.
outputStream.println(
"UIDL " + msgNumber);
uidl = validateOneLine();

//Open an output file to save
// the message. Use the UIDL
// as the file name.
pathFileName = dataPath + uidl;
DataOutputStream dataOut =
new DataOutputStream(
new FileOutputStream(
pathFileName));

//Send a RETR command to begin
// the message retrieval process
outputStream.println(
"RETR " + msgNumber);
//Validate the response.
String retrResponse =
validateOneLine();

//Read the first line in the
// message from the server.
String msgLine =
inputStream.readLine();

//Continue reading lines until
// a "." is encountered as the
// first char in a line. That
// signals the end of the msg.
while(!(msgLine.equals("."))){
//Write the line to the output
// file and read the next
// line. Insert newline
// characters when writing the
// output to the file.
dataOut.writeBytes(
msgLine + "n");
msgLine = inputStream.readLine();

}//end while
//Close the output file. The
// message is now stored in a
// local file with a file name
// based on the unique ID
// provided by the server.
dataOut.close();

//Show progress

textArea.append(msgNumber + "n");

//Increment the message counter
// in preparation for
// processing the next message.
msgCounter++;

//Disable this statement to require
// the user to press the button
// for each new message.
Toolkit.getDefaultToolkit().
getSystemEventQueue().
postEvent(new ActionEvent(
startButton,
ActionEvent.
ACTION_PERFORMED,
"Start/Next"));

}//end if msgNumber <= numberMsgs
else{//msgNumber > numberMsgs
//No more messages. Disable the
//Start/Next button.
startButton.setEnabled(false);

//Alert the user
Toolkit.getDefaultToolkit().beep();
Thread.currentThread().sleep(300);
Toolkit.getDefaultToolkit().beep();
Thread.currentThread().sleep(300);
Toolkit.getDefaultToolkit().beep();
}//end else
}//end try
catch(Exception ex){
ex.printStackTrace();}
}//end actionPerformed
}//end ActionListener
);//end addActionListener

//Configure the GUI by placing the various
// components on it, setting the size and
// making it visible.
add(startButton);
add(textArea);
textArea.setText("");
setLayout(new FlowLayout());

setTitle("Copyright 2004, R.G.Baldwin");
setBounds(274,0,400,400);
//Make the GUI visible.
setVisible(true);
}//end constructor
//===========================================//

//Validate a one-line response.
//The purpose of this method is to confirm that
// the server returned +OK and not -ERR to the
// previous command.
//If +OK, the method returns the string
// returned by the server.
//If -ERR, the method displays the string
// returned by the server and terminates the
// session.
private String validateOneLine(){
try{
String response = inputStream.readLine();
if(response.startsWith("+OK")){
return response;
}else{
System.out.println(response);
//Terminate the session.
outputStream.println("QUIT");
socket.close();
System.out.println(
"Premature QUIT on -ERR");
System.exit(0);
}//end else
}catch(IOException e){
System.out.println("n" + e);}
//The following return statement is requied
// to satisfy the compiler.
return "Make compiler happy";
}//end validateOneLine()
//===========================================//

}//end class VirPro01a
//=============================================//
Listing 40

/*File VirPro01b.java
Copyright 2004, R.G.Baldwin
Rev 02/15/04

This is one of a pair of programs designed to be
used together, in conjunction with a good virus
scanner program to prevent messages with viruses
from contaminating an email inbox.

This program should be run after the program
named VirPro01a has been run, and a virus checker
has been used to confirm that all files produced
by that program are free of viruses, or that any
files containing viruses have been removed from
the system.

See comments at the beginning of VirPro01a.java
for a description of this pair of programs.

This program forwards clean email messages to a
secret email account. On my system, using a
cable modem, about three to four seconds are
required to forward each message to the secret
email account.

This program is designed to be used with a POP3
email server. For technical information on POP3,
see RFC 1725 at
http://www.cis.ohio-state.edu/htbin/rfc/rfc1725.
html

Usage: Enter the following at the command line
(without the line break):
java VirPro01b pubServer userName password
secretServer smtpServer

where the command line parameters identify:
Your public email server
Your user name on the public server
Your password on the public server
Your private email account address
The SMTP server on which you are authorized to
send email messages.

Tested using SDK 1.4.2 under WinXP
************************************************/
import sun.net.smtp.SmtpClient;
import java.net.*;
import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;

class VirPro01b extends Frame{
//The following is the ID of the secret email
// account provided as a command line parameter
// when the program is started.
String recipient;

//The following is the local folder where
// message files are stored awaiting virus
// scanning and forwarding. You may want to
// change this to a different folder.
String dataPath = "./Messages/";
//The following is the local folder where the
// messages are stored after they have been
// scanned and forwarded to the secret email
// account. They are automatically moved to
// this folder after being deleted from the
// public email server. You will want to
// manually empty this folder periodically.
String archivePath = "./Archives/";

//The following are working variables used by
// the program for various purposes.
BufferedReader inputStream;
PrintWriter outputStream;
Socket socket;
String pathFileName;
Vector msgToDelete = new Vector();

public static void main(String[] args){
if(args.length != 5){
System.out.println("Usage: java VirPro01b "
+ "pubServer userName password "
+ "secretServer smtpServer");
System.exit(0);
}//end if

new VirPro01b(args[0],args[1],args[2],
args[3],args[4]);
}//end main
//===========================================//

//Constructor
VirPro01b(final String server,
final String userName,
final String password,
String secretServer,
final String smtpServer){

recipient = secretServer;

//Register a window listener to service
// the close button on the Frame.
this.addWindowListener(
new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}//end windowClosing
}//end WindowAdapter()
);//end addWindowListener

final Button startButton =
new Button("Start");
final Button deleteButton = new Button(
"Delete Msg On Server");
final TextArea textArea =
new TextArea(20,50);

//Register an ActionListener on the
// startButton.
startButton.addActionListener(
new ActionListener(){
public void actionPerformed(
ActionEvent e){
startButton.setEnabled(false);
//Get a directory listing. Make
// certain that dataDir contains only
// message files.
File dataDir = new File(dataPath);
String[] dirList = dataDir.list();

//Now process the files in the
// directory
for(int msgCounter = 0;
msgCounter < dirList.length;
msgCounter++){
String fileName =
dirList[msgCounter];
pathFileName = dataPath + fileName;

//Forward the message to the secret
// email account. You can tag the
// subject with any string that you
// want to pass as the third
// parameter. I will tag with the
// message number as three digits.
// That makes it easy to confirm that
// all messages seccessfully reached
// the secret email account.

String strMsgNumber =
pathFileName.substring(
pathFileName.indexOf(" "),
pathFileName.lastIndexOf(" ")).
trim();
int msgNumber = Integer.parseInt(
strMsgNumber);
String msgNumberStr;
if(msgNumber < 10){
msgNumberStr = "00" + msgNumber;
}else if(msgNumber > 99){
msgNumberStr = "" + msgNumber;
}else{
msgNumberStr = "0" + msgNumber;
}//end else

boolean okToDelete =
forwardEmailMsg(
recipient,
smtpServer,
"{"+ msgNumberStr +"}",
pathFileName);

if(okToDelete){
//Only those messages that were
// successfully forwarded will be
// deleted from the server and
// moved to the archive folder.
textArea.append("Forwarded " +
msgNumberStr + "n");
msgToDelete.add(pathFileName);
}else{
textArea.append("Failed " +
msgNumberStr + "n");
}//end else
}//end for loop on directory length

//Make it possible for the user to
// delete all messages that were
// successfully forwarded to the secret
// email account from the public email
// server.
deleteButton.setEnabled(true);
textArea.append("nDo you want to "
+ "delete messages from server?n");

//Alert the user
try{
Toolkit.getDefaultToolkit().beep();
Thread.currentThread().sleep(300);
Toolkit.getDefaultToolkit().beep();
Thread.currentThread().sleep(300);
Toolkit.getDefaultToolkit().beep();
}catch(Exception ex){
ex.printStackTrace();
}//end catch
}//end actionPerformed
}//end ActionListener
);//end addActionListener

//Register an action listener on the delete
// button
deleteButton.addActionListener(
new ActionListener(){
public void actionPerformed(
ActionEvent e){
deleteButton.setEnabled(false);
textArea.append("n");

//Get connected to the email server
int port = 110; //pop3 mail port
try{
//Get a socket, connected to the
// specified server on the specified
// port.
socket = new Socket(server,port);

//Get an input stream from the socket
inputStream = new BufferedReader(
new InputStreamReader(
socket.getInputStream()));

//Get an output stream to the socket.
outputStream = new PrintWriter(
new OutputStreamWriter(
socket.getOutputStream()),
true);

//Display the msg received from the
// server on the command line screen
// immediately following connection.
String connectMsg =
validateOneLine();
System.out.println(
"Connected to server "
+ connectMsg);

//The communication process is now in
// the AUTHORIZATION state. Send the
// user name and password to the
// server.
//Commands are sent in plain text,
// upper case to the server. Some
// commands require an argument
// following the command, as is the
// case with USER.
//Send the command.
outputStream.println("USER "
+ userName);
//Get response and confirm that the
// response was +OK and was not -ERR.
String userResponse =
validateOneLine();
//Display the response on the
// command line screen.
System.out.println("USER "
+ userResponse);
//Send the password to the server
outputStream.println("PASS "
+ password);
//Validate the server's response as
// +OK. Display the response in the
// process.
System.out.println("PASS "
+ validateOneLine());
}catch(Exception ex){
ex.printStackTrace();
}//end catch

//Delete the collection of messages in
// msgToDelete from the public email
// server. Also transfer the local
// copies of the message files to the
// archive folder.
for(int cnt = 0;
cnt < msgToDelete.size();cnt++){
pathFileName =
(String)msgToDelete.elementAt(cnt);
String strMsgNumber =
pathFileName.substring(
pathFileName.indexOf(" "),
pathFileName.lastIndexOf(" ")).
trim();
int msgNumber = Integer.parseInt(
strMsgNumber);

//Deletion of a message from the
// server is accomplished by marking
// the message for deletion while in
// the TRANSACTION state. The
// message is actually deleted when
// the client sends a QUIT command
// to the server causing the server
// to enter the UPDATE state. If the
// program aborts prematurely before
// sending a QUIT command, marked
// messages are not deleted from the
// server.
//Mark the message for deletion.
System.out.println(
"Deletion is temporarily disabled.");
/*Deletion from the server temporarily disabled.
*Do not enable this deletion code until you
* understand exactly what you are doing.
outputStream.println(
"DELE " + msgNumber);

//Validate the response and display
// it on the GUI.
textArea.append(
"Msg: " + msgNumber + " "
+ validateOneLine()+"n");
textArea.append(
"Marked:" + msgNumber + "n");
*/


//Now move the file that has been
// processed and deleted from the
// server to the archive folder
// on the local disk.
moveFile(pathFileName,archivePath);

}//end for loop on msgToDelete.size()

//Terminate the session with the
// server causing the messages to
// actually be deleted from the server.
outputStream.println("QUIT");
String quitResponse =
validateOneLine();
//Display the response on the
// command line screen.
System.out.println(
"QUIT " + quitResponse);

//Server is now in the UPDATE mode.
// It will delete all files marked
// with the DELE command earlier.
//Close the socket
try{
socket.close();
}catch(Exception ex){
ex.printStackTrace();
}//end catch

textArea.append("nnMessages deleted "
+ "from server.n");
}//end actionPerformed
}//end ActionListener
);//end addActionListener

//Configure the GUI by placing the
// various components on it, setting the size
// and making it visible.
add(startButton);
add(deleteButton);
deleteButton.setEnabled(false);
add(textArea);
textArea.setText("");
setLayout(new FlowLayout());

setTitle("Copyright 2004, R.G.Baldwin");
setBounds(274,0,400,400);
//Make the GUI visible.
setVisible(true);
}//end constructor
//===========================================//

//Validate a one-line response.
//The purpose of this method is to confirm that
// the server returned +OK and not -ERR to the
// previous command.
//If +OK, the method returns the string
// returned by the server.
//If -ERR, the method displays the string
// returned by the server and terminates the
// session.
private String validateOneLine(){
try{
String response = inputStream.readLine();
if(response.startsWith("+OK")){
return response;
}else{
System.out.println(response);
//Terminate the session.
outputStream.println("QUIT");
socket.close();
System.out.println(
"Premature QUIT on -ERR");
System.exit(0);
}//end else
}catch(IOException e){
System.out.println("n" + e);
}//end catch
//The following return statement is requied
// to satisfy the compiler.
return "Make compiler happy";
}//end validateOneLine()
//===========================================//

private boolean forwardEmailMsg(
String recipient,
String smtpServer,
String tag,
String pathFileName){

StringBuffer message = new StringBuffer(
"No message found");

try{
//Pass a string containing the name of
// the smtp server as a parameter to the
// following constructor.
SmtpClient smtp =
new SmtpClient(smtpServer);

//Pass a valid email address to the
// from() method.
smtp.from(recipient);

//Pass the email address of the recipient
// to the to() method.
smtp.to(recipient);

//Get an output stream for the message
PrintStream msg = smtp.startMessage();

//Write the message into the output stream
message = new StringBuffer(readLines(
pathFileName,null,null));

//Insert tag in subject line
message = message.insert(message.indexOf(
"Subject: ")+9,tag);
msg.println(new String(message));
//Close the stream and send the message
smtp.closeServer();

return true;
}catch( Exception e ){
System.out.println("n" + e);
System.out.println("Forwarding email");
Toolkit.getDefaultToolkit().beep();
try{
Thread.currentThread().sleep(300);
}catch(Exception ex){
System.out.println(ex);
}//end catch
Toolkit.getDefaultToolkit().beep();
return false;
}//end catch

}//end forwardEmailMsg
//===========================================//

//Method moves a file from its current location
// specified by pathFileName to a new location
// specified by archivePath.
private void moveFile(String pathFileName,
String archivePath){
String fileName = pathFileName.substring(
pathFileName.lastIndexOf('/') + 1);
String archivePathFileName =
archivePath + fileName;

boolean moved =
new File(pathFileName).renameTo(
new File(archivePathFileName));

if(!moved)System.out.println(
"Unable to move " + new File(pathFileName)
+ "nto " + new File(archivePathFileName));
}//end moveFile method
//===========================================//

//This method reads and saves lines of data
// from a file starting with the line that
// startsWith firstLine and ending with the
// line that startsWith lastLine. If lastLine
// is null, data is saved to the end of the
// file. If firstLine is null, data is saved
// beginning with the first line in the file.
// The lines of data from the file are saved by
// concatenating them into a single string with
// a newline inserted into the string at the
// end of each line.
//The name and path to the file is given by
// pathFileName.
private String readLines(String pathFileName,
String firstLine,String lastLine){
StringBuffer strBuf = new StringBuffer();
try{
BufferedReader inDataMsg
= new BufferedReader(new FileReader(
pathFileName));

String data;
boolean isSave = false;
while((data = inDataMsg.readLine())
!= null){

if(((firstLine == null) ||
(data.startsWith(firstLine))) &&
(isSave == false)){
isSave = true;
}//end if

if(isSave){
strBuf.append(data + "n");
}//end if

if((lastLine != null) &&
(data.startsWith(lastLine))){
break;//no need to read any more
}//end if

}//end while loop
inDataMsg.close();//Close file
}catch(Exception e){e.printStackTrace();}
return new String(strBuf);
}//end readLines
//===========================================//

}//end class VirPro01b
//=============================================//
Listing 41

Copyright 2004, Richard G. Baldwin.  Reproduction in whole or
in
part in any form or medium without express written permission from
Richard
Baldwin is prohibited.

About the author

Richard Baldwin
is a college professor (at Austin Community College in Austin, TX) and
private consultant whose primary focus is a combination of Java, C#,
and XML. In addition to the many platform and/or language independent
benefits of Java and C# applications, he believes that a combination of
Java, C#, and XML will become the primary driving force in the delivery
of structured information on the Web.

Richard has participated in numerous consulting projects, and he
frequently provides onsite training at the high-tech companies located
in and around Austin, Texas.  He is the author of Baldwin’s
Programming Tutorials, which
has gained a worldwide following among experienced and aspiring
programmers. He has also published articles in JavaPro magazine.

Richard holds an MSEE degree from Southern Methodist University
and has many years of experience in the application of computer
technology to real-world problems.

Baldwin@DickBaldwin.com

-end-
 

Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.
Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.

Latest Posts

Related Stories