import java.io.*; import java.net.*; import java.util.*; import java.text.*; /*------------------------------------------------------------------------- * NAME * MailMarpol - Remove garbage from a POP3 account. * * SYNOPSIS * java MailMarpol configfile * * DESCRIPTION * Removes unwanted Emails from a POP3 account. This can be done automatically * by a cronjob once per day. The name reflects the nautical term "Marpol Service" * for removing waste from ships in the harbour according to the Marpol convention * that forbids to throw it over board. * * COMMAND LINE PARAMETERS * configfile name of the configuration file * * INPUT FILES * Configuration file * A list of name = value pairs. These are understood: * popServer = Name of the POP3 server * popUserId = user ID of the POP3 account * popPassWord = password of the POP3 account * blackListDir = directory where the blacklists and whitelists are * garbageDir = directory where the removed garbage is saved * garbageLogFile = log file of garbage removal * verbose = yes or no * lowestMessageNumber = number of the first message to be inspected * Blacklists and whitelists * Their names start with "blacklist-" or "whitelist-" followed by a * header keyword like From, To, Received, Subject and thereafter ".txt". * Instead of a header keyword also "body" is possible. Messages that * contain any string from the corresponding blacklist in the corresponding * header or from blacklist-body.txt in the message body are deleted. * Exception: Messages that contain any string from the corresponding * whitelist in the corresponding header or from whitelist-body.txt * in the body are not deleted. * However, blacklist-Received.txt contains ranges of IP numbers followed * by a complaint address in the syntax nnn.nnn.nnn.0-nnn.nnn.nnn.255 abuse@foo.bar. * You are encouraged to build your own blacklist-Received.txt using whois. An * example grown in many years is http://home.snafu.de/hweede/ubelist.txt. * Additionally, Nigeria UBE is detected and removed. * * OUTPUT FILES * Archived garbage * Each deleted message is copied to the garbage directory, where always the * newest 2000 ones are available and older ones are removed. * Logfile * Each removal is reported as one line of the logfile. The Logfile is * limited to the newest 2000 lines. * * HISTORY * This used to be a tool for moderating the de.markt.arbeit newsgroups, * taking care of the POP3 account of their submission addresses. All * newsgroup related code has been removed. Parallely, it also served to * take care of the authors' private POP3 account for many years. * * AUTHOR * Henning Weede * * TERMS OF USE * You are not allowed to claim that this code is the work of anybody else * than Henning Weede. * The author is not liable for any damage caused by this code. * The author has no duty to waist his time answering questions related * to this code. * The GNU Public License applies. *------------------------------------------------------------------------- */ public class MailMarpol { /* configuration: default or set by configuration file */ public static String popServer; public static String popUserId; public static String popPassWord; public static String blackListDir; public static String garbageDir; public static String garbageLogFile; public static boolean verbose; public static int lowestMessageNumber; /* to skip clean e-mails left on the POP3 server */ /* further global data, not in configuration file */ public static int nbwList=0; /* number of black- and whitelists */ public static String[] bwListName; /* filenames of black- and whitelists */ public static byte[][] bwListContent; /* black- and whitelists as byte arrays */ public static Socket popSocket; public static BufferedReader popSocketReader; public static PrintWriter popSocketWriter; public static int MAXLEN=30; /* maximum lines to download */ public static void main(String[] args) { if(args.length != 1) { System.err.println("usage: java MailMarpol configfile"); System.exit(1); } setDefaultConfiguration(); if(!readConfigurationFile(args[0])) System.exit(1); cleanPopAccount(); } public static void setDefaultConfiguration() /*----------------------------------------------------------------- * This sets member variables of the surrounding class to * default values *----------------------------------------------------------------- */{ popServer = new String("pop.telekommunikackja.pl"); popUserId = new String("Danuta.Filetowa@biuro.pl"); popPassWord = new String("nastorowje"); blackListDir = new String("/home/danuta/ube/lib"); garbageDir = new String("/home/danuta/ube/rejected"); garbageLogFile = new String("/home/danuta/ube/log/rejected.txt"); verbose = true; lowestMessageNumber = 1; } /* setDefaultConfiguration() */ public static boolean readConfigurationFile(String fn) /*----------------------------------------------------------------- * This reads a configuration file whose lines consist of a * variable name followed by = and a value. All or part of the * corresponding member variables of the surrounding class * can be set. Return value: true = success, false = failure *----------------------------------------------------------------- */{ int i; BufferedReader reader; String line; try{ reader = new BufferedReader(new FileReader(fn)); while(true) { line=reader.readLine(); if(line==null) break; i=line.indexOf('='); if(i>0) { if(line.substring(0,i).trim().equalsIgnoreCase("popServer")) popServer=line.substring(i+1).trim(); if(line.substring(0,i).trim().equalsIgnoreCase("popUserId")) popUserId=line.substring(i+1).trim(); if(line.substring(0,i).trim().equalsIgnoreCase("popPassWord")) popPassWord=line.substring(i+1).trim(); if(line.substring(0,i).trim().equalsIgnoreCase("blackListDir")) blackListDir=line.substring(i+1).trim(); if(line.substring(0,i).trim().equalsIgnoreCase("garbageDir")) garbageDir=line.substring(i+1).trim(); if(line.substring(0,i).trim().equalsIgnoreCase("garbageLogFile")) garbageLogFile=line.substring(i+1).trim(); if(line.substring(0,i).trim().equalsIgnoreCase("verbose")) verbose=line.substring(i+1).trim().equalsIgnoreCase("true"); if(line.substring(0,i).trim().equalsIgnoreCase("lowestMessageNumber")) lowestMessageNumber=Integer.parseInt(line.substring(i+1).trim()); } } } catch(Exception e) { System.err.println("When trying to read configuration file "+fn+": "+e); return false; } return true; } /* readConfigurationFile() */ public static int loadBlackLists() /*------------------------------------------------------------------- * Loads blacklists and whitelists to memory. In the directory whose * name ist passed as "blackListDir", blacklist and whitelist files * are expected. Syntax of their names: "blacklist-" or "whitelist-", * respectively, followed by an e-mail or usenet header keyword * (case sensitive) and after that ".txt". Black- or whitelists concerning * the message body are described by the pseudo keyword "body". * Each line of a blacklist is a string that causes the e-mail to be * rejected if it is found in the corresponding header line. * Each line of a whitelist is a string that prevents the e-mail from * being rejected (even if blacklists match) if it is found in the * corresponding header line. However, blacklist-Received.txt is different: * Its lines have the syntax nnn.nnn.nnn.0-nnn.nnn.nnn.255 abuse@foo.bar * and contain blacklisted ranges of IP addresses. Recommended * example of a balcklist-Received.txt to clean a private POP3 account * is http://home.snafu.de/hweede/ubelist.txt. * * Member variables of the surrounding class: * This requires: * Strig blackListDir = directory where the black- and whitelists are * This sets: * int nbwList = number of black- and whitelists * String[] bwListName = filenames of black- and whitelists * byte[][] bwListContent = black- and whitelists as byte arrays * * Return value: 0=success, 1=failure *------------------------------------------------------------------- */{ int i; File dir; String[] fn; BufferedReader listFileReader; ByteArrayOutputStream baStream; PrintWriter listArrayWriter; String listLine; try{ /* obtain "fn" = string array of all files in directory */ dir = new File(blackListDir); fn = dir.list(); /* count "nbwList" = how many of them are black- or whitelists */ for(i=nbwList=0; i9) if( (fn[i].substring(0,9).equals("blacklist")) || (fn[i].substring(0,9).equals("whitelist")) ) ++nbwList; } if(nbwList==0) { System.err.println("No black- or whitelists found in " + blackListDir); return 1; } /* allocate "bwListName" = array of filenames and "bwListContent" = array of byte arrays */ bwListName = new String[nbwList]; bwListContent = new byte[nbwList][]; /* loop over all files in directory */ for(i=nbwList=0; i9) if( (fn[i].substring(0,9).equals("blacklist")) || (fn[i].substring(0,9).equals("whitelist")) ){ /* bwListName = subset of fn, only black- and whitelist files */ bwListName[nbwList] = fn[i]; /* read file and save it as byte array */ listFileReader = new BufferedReader(new FileReader(new File(dir,bwListName[nbwList]))); baStream = new ByteArrayOutputStream(); listArrayWriter = new PrintWriter(new OutputStreamWriter(baStream)); while(true) { listLine=listFileReader.readLine(); if(listLine==null) break; listArrayWriter.println(listLine); } /* determine correct byte array address */ listArrayWriter.flush(); bwListContent[nbwList] = baStream.toByteArray(); listFileReader.close(); ++nbwList; } /* end of condition: black- or whitelist */ } /* end of loop over all files */ } catch(Exception e) { System.err.println("Cannot load blacklists to memory: " + e); return 1; } return 0; } /* loadBlackLists() */ static void cleanPopAccount() /*----------------------------------------------------------------------------------- * Removes garbage fom a POP3 account driven by msgInspect(). *----------------------------------------------------------------------------------- */{ int i,j,imsg,l,ngarbage,nlog; int[] garbage = new int [2048]; String mailLine,logLine; BufferedReader logReader; PrintWriter msgArrayWriter,logWriter; ByteArrayOutputStream baStream; byte[] msgByteArray; File garbageFile; /* copy blacklists from files to byte arrays "bwListContent[][]" */ if(loadBlackLists()!=0) return; /* limit the saved copies and the garbage logfile to the newest 3000 */ try{ logReader = new BufferedReader(new FileReader(garbageLogFile)); for(nlog=0;;++nlog) if(logReader.readLine()==null) break; logReader.close(); if(nlog>3000) { nlog=nlog-3000; logReader = new BufferedReader(new FileReader(garbageLogFile)); baStream = new ByteArrayOutputStream(); logWriter = new PrintWriter(new OutputStreamWriter(baStream)); for(i=0; i3) l=3; if(!mailLine.substring(0,l).equals("+OK")) { if(verbose&&(imsg==lowestMessageNumber)) System.out.println("No new e-mails for "+popUserId+" on "+popServer); break; } /* loop over the lines of this message */ for(;;) { /* read next line, copy it to byte array, break if "." */ mailLine=popSocketReader.readLine(); if(mailLine==null) { System.err.println("Cannot read from POP3 server while downloading."); throw new Exception(); } msgArrayWriter.println(mailLine); if(mailLine.equals(".")) break; } /* end of loop over e-mail lines */ /* save the byte array address of this message */ msgArrayWriter.flush(); msgByteArray=baStream.toByteArray(); /* inspect this e-mail */ if(msgInspect(imsg,msgByteArray)) { garbage[ngarbage]=imsg; ++ngarbage; } } /* end of loop over the e-mails */ catch(Exception e) { System.err.println("Cannot read mail no. " + imsg + ". " + e); return; } } /* remove the garbage and quit */ i=-1; try{ for(i=0; i3) l=3; if((mailLine==null)||(!mailLine.substring(0,l).equals("+OK"))) { System.err.println("When removing mail no. " + garbage[i] + ", no response with +OK from POP3 server."); throw new Exception(); } } } catch(Exception e) { if(i>=0) System.err.println("Cannot remove mail no. " + garbage[i] + ". " + e); return; } /* close the socket */ pop3Close(); return; } /* cleanPopAccount() */ static public boolean msgInspect(int imsg,byte[] msgByteArray) /*------------------------------------------------------------------- * Inspects an e-mail message contained in the byte array msgByteArray * and returns whether it is garbage or not. This is driven by the following * member variables of the surrounding class: * public static int nbwList=0 = number of black- and whitelists * public static String[] bwListName = filenames of black- and whitelists * public static byte[][] bwListContent = black- and whitelists as byte arrays * public static String garbageDir = directory where garbage is saved * public static String garbageLogFile = logfile of garbage removal * The name of a blacklist is composed as "blacklist-"+keyword+".txt". * The name of a whitelist is composed as "whitelist-"+keyword+".txt". * If a header line of the message matches the corresponding blacklist * and no header line matches its corresponding whitelist the message is garbage. * To match means that any line of the list is found to be a substring in the * corresponding message header. The message body is handled like a header with * The keyword "body". Exception: the blacklist of "Received" headers has the * syntax "nnn.nnn.nnn.0-nnn.nnn.nnn.255 abuse@foo.bar" to represent ranges of * blacklisted IP numbers. * If the message is garbage, a copy of it is stored in the garbageDir directory * and the garbage logfile is updated. *------------------------------------------------------------------- */{ boolean inheader,isBounceMessage,remove_this_mail; char auf,zu; int i,i0,i1,i2,ibList,iwList,iter,nip; int foundNigeriaWords,totalNigeriaWords; long lowerip,upperip,iltmp; long[] ipInReceived = new long[32]; String keyWord,prevKeyWord,listLine,mailLine,fnCopy; String date,from,subject,illegalHeader,illegalString; BufferedReader listReader,msgReader; PrintWriter garbageWriter,logFileWriter; DecimalFormat fmt2 = new DecimalFormat("00"); DecimalFormat fmt4 = new DecimalFormat("0000"); Calendar cal = Calendar.getInstance(); /* noch unbenutzt, weiterbasteln */ String[] bounceSeparator={ "This is a copy of the message's headers.", "Message headers follow", "Message header follows", "" }; /* to make the compiler shut up */ inheader=true; isBounceMessage=false; remove_this_mail=false; nip=0; mailLine=listLine=date=from=subject=illegalHeader=illegalString="voidmissing"; /* reset Nigeria UBE */ foundNigeriaWords=totalNigeriaWords=0; if(verbose) System.out.print(imsg + " "); try{ /* open message byte array for reading */ msgReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(msgByteArray))); /* blacklist tests: loop over the lines of this message */ for(inheader=true,isBounceMessage=false,keyWord=prevKeyWord="void",remove_this_mail=false;;prevKeyWord=keyWord) { /* read next line, switch from header to body mode if empty, break if "." */ mailLine=msgReader.readLine(); if(mailLine==null) break; if(mailLine.equals(".")) break; if(mailLine.equals("")) { inheader=false; mailLine=msgReader.readLine(); if(mailLine==null) break; if(mailLine.equals(".")) break; } if(!inheader) if( (mailLine.startsWith("Content-Type: ")&&(mailLine.toLowerCase().indexOf("/rfc822")>0)) || (mailLine.indexOf("Message header follows")>=0) || (mailLine.indexOf("Message headers follow:")>=0) || (mailLine.indexOf("This is a copy of the message's headers.")>=0) ) { mailLine=msgReader.readLine(); if(mailLine.startsWith("Content-")) mailLine=msgReader.readLine(); if(mailLine==null) break; if(mailLine.equals(".")) break; if(mailLine.trim().equals("")) { inheader=true; isBounceMessage=true; mailLine=msgReader.readLine(); if(mailLine==null) break; if(mailLine.equals(".")) break; } } /* set or change current header keywod or "body" if body; gather material for logfile */ if(inheader) { if(mailLine.charAt(0)>' ') { i=mailLine.indexOf(':'); if(i>0) { keyWord=mailLine.substring(0,i); if(mailLine.length()>i+1) if(!mailLine.substring(i+1).trim().equals("")) { if(keyWord.equalsIgnoreCase("Date")) date=mailLine.substring(5).trim(); if(keyWord.equalsIgnoreCase("From")) from=mailLine.substring(5).trim(); if(keyWord.equalsIgnoreCase("Subject")) subject=mailLine.substring(8).trim(); } } } } else keyWord="body"; /* look for garbage only if none found yet; otherwise, * further reading only serves to find info for logfile */ if(!remove_this_mail) { /* look for blacklist belonging to this keyword */ for(i=0, ibList= -1; i=0) { if((i2=mailLine.indexOf(zu,i1))>i1) { iltmp=ips2l(mailLine.substring(i1+1,i2)); if(iltmp>=0L) { ipInReceived[nip]=iltmp; if(nip0)&&(i2>i1)) { lowerip=ips2l(listLine.substring(0,i1)); upperip=ips2l(listLine.substring(i1+1,i2)); if((lowerip<0L)||(upperip<0L)) System.err.println("broken blacklist-Received.txt: " + listLine); for(i=0; i=lowerip)&&(ipInReceived[i]<=upperip)) { remove_this_mail=true; illegalHeader=keyWord; illegalString=listLine; break; } } } else System.err.println("broken blacklist-Received.txt: " + listLine); } else { if(mailLine.indexOf(listLine)>=0) { remove_this_mail=true; illegalHeader=keyWord; illegalString=listLine; break; } } } /* end of loop over blacklist */ } /* end of condition: yes, blacklist of this keyword available */ /* look for blacklist belonging to Nigeria UBE */ for(i=0, ibList= -1; i=0) ++foundNigeriaWords; } /* end of loop over blacklist */ } /* end of condition: yes, blacklist of Nigeria UBE available */ } /* end of condition: no garbage found yet */ /* stop inspecting this e-mail if it's garbage already and if header has been read */ if(remove_this_mail&&(!inheader)) break; } /* end of loop over e-mail lines */ /* if not yet blacklisted, check if this is Nigeria UBE */ if((!remove_this_mail)&&(foundNigeriaWords>0)&&(totalNigeriaWords>0)) { if((double)foundNigeriaWords/(double)totalNigeriaWords>0.26) { remove_this_mail=true; illegalHeader="body"; illegalString="Nigeria UBE wordlist"; if(verbose) System.out.print(" (Nigeria-Uebereinstimmung: " + 100.*(double)foundNigeriaWords/(double)totalNigeriaWords + " %) "); } } /* don't remove complaints that bounced back */ if(remove_this_mail&&isBounceMessage&&(subject.indexOf("UBE")>=0)) { remove_this_mail=false; } /* if blacklisted, check if whitelisted */ if(remove_this_mail) { /* open message byte array for reading again */ msgReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(msgByteArray))); /* whitelist tests: loop over the lines of this message */ for(inheader=true,keyWord=prevKeyWord="void";;prevKeyWord=keyWord) { /* read next line, switch from header to body mode if empty, break if "." */ mailLine=msgReader.readLine(); if(mailLine==null) break; if(mailLine.equals(".")) break; if(mailLine.equals("")) { inheader=false; mailLine=msgReader.readLine(); if(mailLine==null) break; if(mailLine.equals(".")) break; } /* set or change current header keywod or "body" if body */ if(inheader) { if(mailLine.charAt(0)>' ') { for(i=0; i=0) { System.out.println("whitelisted: " + listLine + " in " + keyWord); remove_this_mail=false; break; } } /* end of loop over whitelist */ } /* end of condition: yes, whitelist of this keyword available */ /* stop inspecting this e-mail if it's whitelisted already */ if(!remove_this_mail) break; } /* end of loop over e-mail lines */ } /* end of condition: yes, blacklists match and whitelist check required */ } catch(Exception e) { System.err.println(" msgInspect(): " + e); return false; } /* check if all strings are defined, otherwise don't report garbage */ if(remove_this_mail) { /* $$$ * if(date.equals("voidmissing")) * { * System.err.println("missing Date header"); * remove_this_mail=false; * } * if(from.equals("voidmissing")) * { * System.err.println("missing From header"); * remove_this_mail=false; * } */ if(illegalHeader.equals("voidmissing")) { System.err.println(" missing information what header is illicit"); remove_this_mail=false; } if(illegalString.equals("voidmissing")) { System.err.println(" missing information why message is garbage"); remove_this_mail=false; } } /* if removable, update logfile and save copy */ if(remove_this_mail) { /* generate file name from month, day, hour, minute, second and message number */ fnCopy=fmt2.format(cal.get(Calendar.MONTH)+1)+fmt2.format(cal.get(Calendar.DAY_OF_MONTH))+fmt2.format(cal.get(Calendar.HOUR_OF_DAY))+fmt2.format(cal.get(Calendar.MINUTE))+fmt2.format(cal.get(Calendar.SECOND))+fmt4.format(imsg)+".txt"; /* update logfile */ try{ logFileWriter = new PrintWriter(new BufferedWriter(new FileWriter(garbageLogFile,true))); logFileWriter.println(fnCopy + "\t" + date + "\t" + from.replace('\t',' ') + "\t" + subject.replace('\t',' ') + "\t" + illegalHeader + " matches " + illegalString); logFileWriter.flush(); logFileWriter.close(); } catch(Exception e) { System.err.println(" Cannot update logfile for removed message " + imsg); } /* save copy, appending reason to reject after the "." line */ try{ garbageWriter = new PrintWriter(new BufferedWriter(new FileWriter(new File(garbageDir,fnCopy)))); msgReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(msgByteArray))); while(true) { mailLine=msgReader.readLine(); if(mailLine==null) break; garbageWriter.println(mailLine); if(mailLine.equals(".")) break; } garbageWriter.println("Rejected because " + illegalHeader + " matches " + illegalString); if(verbose) { System.out.println("rejected."); System.out.println(" From: " + from); System.out.println(" Subject: " + subject); System.out.println(" Problem: " + illegalHeader + " matches " + illegalString); System.out.println(" Saved as: " + fnCopy); } garbageWriter.flush(); garbageWriter.close(); } catch(Exception e) { System.err.println(" Cannot save copy of removed message " + imsg); } } else if(verbose) System.out.println("OK"); return remove_this_mail; } /* msgInspect() */ public static long ips2l(String s) /*------------------------------------------------------------------- * Converts IPv4 address from string to 4 byte number. * -1L if error *------------------------------------------------------------------- */{ int i,j,k; long il; int p[] = new int[4]; for(k=0, j=1; k<4; ++k) { if(k<3) p[k]=s.indexOf('.',j); else p[k]=s.length(); if((p[k]j+2)) return -1L; j=p[k]+2; } for(i=0,j= -1; i<4; ++i) for(++j; j'9')) return -1L; for(j=k=0,il=0L; k<4; ++k) { i=Integer.parseInt(s.substring(j,p[k])); if((i<0)||(i>255)) return -1L; il=(il<<8)|(long)i; j=p[k]+1; } return il; } /* ips2l() */ static boolean pop3Open() /*----------------------------------------------------------------- * Opens a POP3 socket based upon the member variables of the same class: * String popServer, popUserId, popPassWord. * Sets the member variables of the same class: * popSocket, popSocketReader, popSocketWriter; *----------------------------------------------------------------- */{ int l; String mailLine; try{ /* open POP3 socket */ popSocket = new Socket(popServer,110); popSocketReader = new BufferedReader(new InputStreamReader(popSocket.getInputStream())); popSocketWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(popSocket.getOutputStream()))); mailLine= popSocketReader.readLine(); if(mailLine==null) { System.err.println("Cannot read from POP3 server initially."); throw new Exception(); } l=mailLine.length(); if(l>3) l=3; if(!mailLine.substring(0,l).equals("+OK")) { System.err.println("POP3 server starts saying: " + mailLine); throw new Exception(); } /* login */ popSocketWriter.println("USER " + popUserId); popSocketWriter.flush(); mailLine= popSocketReader.readLine(); if(mailLine==null) { System.err.println("Cannot read from POP3 server after USER part of login."); throw new Exception(); } l=mailLine.length(); if(l>3) l=3; if(!mailLine.substring(0,l).equals("+OK")) { System.err.println("Upon login attempt (USER), POP3 server says: " + mailLine); throw new Exception(); } popSocketWriter.println("PASS " + popPassWord); popSocketWriter.flush(); mailLine= popSocketReader.readLine(); if(mailLine==null) { System.err.println("Cannot read from POP3 server after PASS part of login."); throw new Exception(); } l=mailLine.length(); if(l>3) l=3; if(!mailLine.substring(0,l).equals("+OK")) { System.err.println("Upon login attempt (PASS), POP3 server says: " + mailLine); throw new Exception(); } } catch(Exception e) { System.err.println("Connection to " + popServer + " failed: " + e); return false; } return true; } /* pop3Open() */ static void pop3Close() /*----------------------------------------------------------------- * Closes the POP3 socket associated with these member variables * of the same class: * popSocket, popSocketReader, popSocketWriter; *----------------------------------------------------------------- */{ int l; String mailLine; try{ popSocketWriter.println("QUIT"); popSocketWriter.flush(); mailLine=popSocketReader.readLine(); popSocketWriter.close(); popSocketReader.close(); popSocket.close(); } catch(Exception e) { System.err.println("Closing socket failed: " + e); } } /* pop3Close() */ static long csum(byte[] msg) /*-------------------------------------------------------------------- * Returns checksum of the message contained in the byte array. * The checksum is defined in a manner that two identical contents * return the same checksum, no matter how they are formatted. *------------------------------------------------------------------- */{ boolean inbody; int i; long sum; String line; char[] c; BufferedReader msgReader; sum=0L; inbody=false; try{ msgReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(msg))); for(;;) { line=msgReader.readLine(); if(line==null) break; if(line.equals("")) inbody=true; if(inbody) { c=line.toUpperCase().toCharArray(); for(i=0; i='A')&&(c[i]<='Z')) || ((c[i]>='0')&&(c[i]<='9'))) sum+=c[i]; } } } catch(Exception e){} return sum; } /* csum() */ }