/*
originaly written before 2000
resurected for 2004
rednuht

simple fake single threaded pop3 server, uses file / direcory and accepts any user/password
Now with threading so it can run all day long

as with most of my source code this is released under the !GNU licencing system, you should not have recived a copy in this distribution archive.

feel free to hack this to your needs.
new information may or may not be available at 
http://www.jumpstation.co.uk/javapopper/

Jan 2005 finaly sees proper threading (static threads do not work) and OUTLOOK XP compatibility !!

Now I need to tidy up and impliment a simple USER and PASSword system, then it will be a really POP3 server instead of a hack ;)
*/

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


// this class is the one called from the command line
class popper {
 	public static void main(String[] args) {
  		Socket s = null;
		PrintStream ps;
  		DataInputStream dis;
  		Vector popThreads = new Vector();
  		try {
   			ServerSocket serverConnect = new ServerSocket(110);
   			System.out.println("POPPER..");
   			System.out.println("..");
   			System.out.println("server created on " + serverConnect.getLocalPort());
   			while (true) {
    			System.out.println("POPPER.. ");
    			s = serverConnect.accept();
    			System.out.println("POPPER... ");
    			// at this point we have the connection and the input / output streams setup 
    			// so we just dump them on a server thread
    			popThreads.addElement(new telpopserver(s));
    			// rinse and repeat as neccessary
    			System.out.println("POPPER.. awainting NEW connection");
   			}
  		} catch (IOException e) {
   			System.out.println(e);
  		}
		System.out.println("POPPER.. You should Never, I mean never see this message (except in the source).");
 	}
} // end class popper

class telpopserver extends Thread {
 	Socket s;
 	PrintStream ps;
	DataInputStream dis;
	String cmdUser="user";
 	String cmdPass="pass";
 	String cmdList="list";
 	String cmdRetr="retr";
	String cmdStat="stat";
	String cmdRset="rset";
 	String cmdDele="dele";
 	String cmdTop ="top";
	String cmdUidl="uidl";

 	String dropzone="/var/tunnel/.";				// my setup specific, should be configurable some how
 	String deadzone="/var/tunnel/tunneled/.";	// ditto

 	int totalNumberOfMessages;
 	long totalSizeOfWaitingMessages;
 	Vector messages = new Vector();

 	public telpopserver (Socket sockee) {
  		s   = sockee;  
  		try {
      		ps = new PrintStream(s.getOutputStream());
   			dis = new DataInputStream(s.getInputStream());
   			this.start();
  		} catch (IOException e) {
   			System.out.println(e);
  		}
 	}
 
 	public void run() {
  		//loadMessages();
  		int messageNo;
  		String reply;
  		if (1==2) {
  	 		//shutdownPhase();
  		} else {
   			sendMsg("+OK Welcome to POPPER v1.05");
   			reply = getMsg();
   			String mode ="AUTH1";
   			while (reply.equals("quit") == false) {
    			yield();
    			
    			if (reply.startsWith(cmdUser)&& mode.equals("AUTH1")) {
     				if (reply.length() > 1+cmdUser.length()) {
      					sendMsg("+OK Password required for "+reply.substring(1+cmdUser.length(),reply.length()));
      					mode="AUTH2";
     				} else {
      					sendMsg("-ERR Expected user name");
     				}
     				
    			} else if (reply.startsWith(cmdPass)&& mode.equals("AUTH2")) {
     				if (reply.length() > 1+cmdPass.length()) {
      					sendMsg("+OK password accepted "+reply.substring(1+cmdPass.length(),reply.length()));
      					loadMessages();
      					mode="TRANS";
     				} else {
      					sendMsg("-ERR Password Supplied is incorrect");
     				}
     				
    			// login phase complete (AUTH1/AUTH2)
    			} else if(reply.startsWith(cmdStat)&& mode.equals("TRANS")) {
     				sendMsg("+OK " + totalNumberOfMessages + " " + totalSizeOfWaitingMessages);
     				
    			} else if(reply.startsWith(cmdRset)&& mode.equals("TRANS")) {
     				sendMsg("+OK But not supported and we are not telling you");
     				
    			} else if(reply.startsWith(cmdDele)&& mode.equals("TRANS")) {
     				// get message number
     				messageNo = Integer.parseInt(reply.substring(1+cmdDele.length(),reply.length()),10);
			     	// delete file - set unread status to false
     				messageFile mess = (messageFile)messages.elementAt(messageNo-1);
     				mess.unread = false;
     				sendMsg("+OK Message " + messageNo + " deleted");

    			} else if (reply.startsWith(cmdList)&& mode.equals("TRANS")) {
     				// list all messages by order and size
     				for (int i = 0; i < totalNumberOfMessages;i++) {
      					messageFile mess = (messageFile)messages.elementAt(i);
      					if (mess.unread==true) {
       						sendMsg("+OK " + (1+i) + " " + mess.size); // 0 based vector array
      					}
     					System.out.println(totalNumberOfMessages + " messages to list, listing message " + i);
     				}
				    sendMsg(".");
    	
    			} else if(reply.startsWith(cmdRetr)&& mode.equals("TRANS")) {
     				String fn;
     				messageNo = Integer.parseInt(reply.substring(1+cmdRetr.length(),reply.length()),10);
     				// get message number
     				sendMsg("+OK Message " + messageNo + " follows");
     				messageFile mess = (messageFile)messages.elementAt(messageNo-1);
     				fn = mess.name;
     				// open file
     				try {
      					File N_dataFile = new File(dropzone,fn);
      					FileInputStream I_dataFile = new FileInputStream(N_dataFile);
      					BufferedReader  b = new BufferedReader(new InputStreamReader(I_dataFile));
      					String dl = b.readLine();
      					while (dl!=null) {
       						sendMsg(dl);
       						// get next line of data
       						dl = b.readLine();
       						// follow rfc byte stuffing rules
       						if (dl!=null) { // otherwise the next line gets compared with null
        						if (dl.startsWith(".")) { dl = "." + dl; }
       						}
      					}
      					// close file
      					I_dataFile.close();
     				} catch (FileNotFoundException e) {
       					System.err.println("file not found " + dropzone + fn);
              		} catch (IOException e) {
               			System.err.println("Reading data file :"+ e);
           			}
     				// at eof
     				// send blank line
     				sendMsg("");
     				// send dot
     				sendMsg(".");

    			} else if(reply.startsWith(cmdTop)&& mode.equals("TRANS")) {
     				String fn;
				    boolean finishedheaders=false;
				    int numlines=0;
     				// get message number
     				// get number of lines (+1)
     				messageNo = Integer.parseInt(reply.substring(1+cmdTop.length(),reply.lastIndexOf(" ")),10);
     				numlines  = Integer.parseInt(reply.substring(1+reply.lastIndexOf(" "),reply.length()),10);
     				// get message number
     				sendMsg("+OK Message " + messageNo + " " + numlines + " lines follows");
     				messageFile mess = (messageFile)messages.elementAt(messageNo-1);
     				fn = mess.name;
     				// open file
     				try {
      					File N_dataFile = new File(dropzone,fn);
      					FileInputStream I_dataFile = new FileInputStream(N_dataFile);
      					BufferedReader  b = new BufferedReader(new InputStreamReader(I_dataFile));
      					String dl = b.readLine();
      					// while not eof and number of lines AFTER the header not sent, send line by line
      					while (dl!=null && numlines!=0) {
       						
       						// get next line of data
       						dl = b.readLine();
       						// follow rfc byte stuffing rules
       						if (dl!=null) { // otherwise the next line gets compared with null
        						if (dl.startsWith(".")) { dl = "." + dl; }
	       						if (finishedheaders) { 
	       							sendMsg(dl);
	       							numlines--; 
	       						}
        						if (dl.length()<1) { finishedheaders=true; }
							}
       						//System.out.println(dl.length() + "%[" + dl + "] " + finishedheaders );
      					}
      					// close file
      					I_dataFile.close();
     				} catch (FileNotFoundException e) {
       					System.err.println("file not found " + dropzone + fn);
              		} catch (IOException e) {
               			System.err.println("Reading data file :"+ e);
           			}
     				// at eof
     				// send blank line
     				sendMsg("");
     				// send dot
     				sendMsg(".");

    			} else if (reply.startsWith(cmdUidl)&& mode.equals("TRANS")) {
    				messageNo=-1;
    				if (cmdUidl.length()!=reply.length()) {
	     				messageNo = Integer.parseInt(reply.substring(1+cmdRetr.length(),reply.length()),10);
	     			}
	     			if (messageNo==-1) {
	     				// list all messages by order and size
						sendMsg("+OK");
	     				for (int i = 0; i < totalNumberOfMessages;i++) {
	      					messageFile mess = (messageFile)messages.elementAt(i);
	      					if (mess.unread==true) {
	       						sendMsg((1+i) + " " + mess.name); // 0 based vector array
	      					}
	     				}
					    sendMsg(".");
					} else {
						// just the one
						messageFile mess = (messageFile)messages.elementAt(messageNo);
      					if (mess.unread==true) {
       						sendMsg("+OK " + (1+messageNo) + " " + mess.size); // 0 based vector array
      					} else {
      						sendMsg("+ERR uidl message no available");
      					}
    				}
    			} else {
     				sendMsg("-ERR Unknown command [" + reply + "] in mode [" + mode + "]");
    			}
   		 		reply = getMsg();
   			}
   		sendMsg("+OK POPPER signing off");
   		ps.flush();
   		try {
    		s.close();
   		} catch (IOException e) {
    		System.out.println(e);
   	}
   	shutdownPhase();
//   this.stop();
  } // dodgy if
 }



 	// delete read messages (or just move them to another folder
 	public void shutdownPhase() {
  		// for each read message delete the file (or move it elsewhere)
  		boolean status;
  		for (int i = 0; i < totalNumberOfMessages;i++) {
   			messageFile mess = (messageFile)messages.elementAt(i);
   			if (mess.unread==false) {
    			System.out.print("Backing up [" + mess.name);
    			File origFile = new File(dropzone,mess.name);
    			File destFile = new File(deadzone,mess.name);
    			status = origFile.renameTo(destFile);
    			System.out.println("]  [" + status + "]");
   			}
  		}
  		System.out.println("This session has been completed and will now be shut down, see you on the flip side !");
 	}


 	// load message details into vector array
 	public void loadMessages() {
  		long fs;
  		totalNumberOfMessages = 0;
  		totalSizeOfWaitingMessages = 0;
  		// get directory
  		File cdir = new File(dropzone);
		//  File cdir = new File(".");
  		String clist[] = cdir.list();
  		// for some reason Outlook express ignores the first message
  		messages.addElement(new messageFile("nonexistant.message",0));
  		totalNumberOfMessages++;
  		// get file name and size
  		for (int i = 0; i < clist.length; i++) {
   			File ccurrent = new File(dropzone,clist[i]); // note the direcory ! if this is not here the clist item does not have enough info to find the real file unless you run from the directory !
   			if (!ccurrent.isDirectory()) {
    			fs=ccurrent.length();
    			System.out.println(clist[i] + " " + fs + " bytes");
    			// add to vector
    			messages.addElement(new messageFile(clist[i],fs));
    			totalNumberOfMessages++;
    			totalSizeOfWaitingMessages+=fs;
   			} else {
    			System.out.println("Ignoring Directory [" + clist[i] + "]");
   			}
  		}
  		System.out.println("Total files found " + totalNumberOfMessages + " with a combinted size of " + totalSizeOfWaitingMessages + " bytes");
 	}


 	public void sendMsg(String sMsg) {
  		ps.print(sMsg + "\r\n");
  		ps.flush();
  		System.out.println("Tx->["+sMsg+"]->");
 	}

 	public String getMsg() {
  		String rMsg;
  		try {
   			rMsg = dis.readLine();
  		} catch (IOException e) {
   			System.out.println(e);
   			rMsg="ERROR - No Data Read";
  		}
  		if (rMsg==null) {
   			rMsg="quit";
  		} else {
   			System.out.println("Rx<-["+rMsg+"]<-");
  		}
  		return(rMsg.toLowerCase());
 	}


} // End of POPPER class definition



// data class
class messageFile {
 	long size;
 	String name;
 	boolean unread;

 	public messageFile(String fn, long fs) {
  		name = fn;
  		size =fs;
  		unread=true;
 	}

} // end of data class
