October 2, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Enlisting Java in the War Against Email Viruses

  • March 2, 2004
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

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("\n\nMessages 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("\n\nMessages 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-
 







Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel