/* FARGOS Development, LLC Sample Programs Copyright (C) 2002 FARGOS Development, LLC. All rights reserved. mailto:support@fargos.net for assistance. NOTE: in the future, enhanced versions of these classes may be added to the FARGOS/VISTA Object Management Environment core within the Standard namespace. Developers can avoid any potential conflict if they specify an explicit namespace when creating instances of the sample classes. */ %include global POP3 { const string POP3_SERVER_ID = "FARGOS/VISTA POP3 Server 1.0"; const int MAX_IDLE_TIME = 600; }; class Local . POP3server { /*! Implements an RFC 1939 server. !*/ assoc configParams; oid listenObj; int connectionsAccepted; } inherits from Object; POP3server:create(string listenAtAddr, string hostName) { string smtpHostName, listenAddress; if (length(hostName) > 0) { smtpHostName = hostName; } else { smtpHostName = getSystemInfoAttribute("hostName"); } configParams["hostName"] = smtpHostName; if (typeOf(listenAtAddr) == string) { listenAddress = listenAtAddr; } else { listenAddress = "tcp:0.0.0.0:110,l"; } assoc connACL = makePermitEveryoneACL(); assoc acl = makeDefaultACL(); listenObj = send "createObject"("AcceptConnection", acl, listenAddress, thisObject, connACL) to ObjectCreator; send "addNotifyOnShutdown"(thisObject) to "ShutdownService"; logOutput(LOG_INFO, "POP3server operating on ", listenAddress, "\n"); } POP3server:delete() { if (listenObj != nil) { send "deleteYourself" to listenObj; } } POP3server:systemShutdown() { /*! Because it provides a long-running service, class=POP3server objects register themselves with the class=ShutdownService, which sends a method=systemShutdown notification when a graceful shutdown is requested. !*/ // TO DO: consider deletion without shutdown... send "deleteYourself" to thisObject; } POP3server:connectionAccepted(oid newSocket) { /*! When new HTTP connections are received, the method=connectionAccepted method creates a class=HTTPfastReceive object to handle the I/O and processing of any requests. !*/ // becomePseudoUser(); connectionsAccepted += 1; assoc acl = makeDefaultACL(); send "createObject"("POP3clientConnection", acl, newSocket, configParams) to ObjectCreator from nil; // don't bother with response } class Local . POP3clientConnection { enum ParseStates { AUTHORIZATION, TRANSACTION, UPDATE, DONE }; oid connObj; oid readBfr; oid mailboxService; string peerAddress; string claimedPeerName; int lastRequestTime; assoc serverConfig; oid timerObj; int currentParseState; int authenticated; int sessionClosed; oid shutdownOID; oid userMailbox; array messageIdList; set messagesToDelete; } inherits from Object; POP3clientConnection:create(oid newSocket, assoc configParams) { connObj = newSocket; serverConfig = configParams; peerAddress = send "getPeerAddress" to connObj; display("New POP3 connection from ", peerAddress, "\n"); mailboxService = lookupLocalService("MailboxDirectory"); if (mailboxService == nil) { send "writeBytes"("-ERR No MailboxDirectory\r\n") to connObj from nil; send "deleteYourself" to thisObject; exit; } lastRequestTime = getLocalRelativeTime(); assoc currentTime = convertLocalRelativeTimeToAbsolute(lastRequestTime, 0); string date = rfc1123Date(currentTime); string initialLine = makeAsString("+OK POP3 server ", serverConfig["hostName"], " ", POP3_SERVER_ID, "; local time is ", date, "\r\n"); int rc = send "writeBytes"(initialLine) to connObj; display("wrote len=", rc, "\n"); if (rc <= 0) { display("Could not announce to client: ", initialLine, "\n"); send "deleteYourself" to connObj; send "deleteYourself" to thisObject; exit; } currentParseState = AUTHORIZATION; assoc acl = makeDefaultACL(); readBfr = send "createObject"("ReadBuffer", acl, connObj) to ObjectCreator; // NOTE: end of line delimeter must be set to CR/LF for correctness. // RFC 2821, section 4.1.1.4 requires that a single LF MUST NOT // be acccepted, even if it would seem to tolerate incorrect // implementations. This is one case where being tolerant in // what one accepts is explicitly disallowed. send "setDelimeter"("\r\n") to readBfr; send "selectForRead" to readBfr; timerObj = send "createObject"("TimerEvent", acl, thisObject, MAX_IDLE_TIME) to ObjectCreator; // TO DO: only permit systemShutdown method // shutdownOID = thisObject; // send "addNotifyOnShutdown"(shutdownOID) to "ShutdownService"; } POP3clientConnection:delete() { if (readBfr != nil) { send "deleteYourself" to readBfr; } if (shutdownOID != nil) { send "removeNotifyOnShutdown"(shutdownOID) to "ShutdownService"; } } POP3clientConnection:timerExpired(oid timerObj) { if (sessionClosed == 1) { display("Session was closed, time to delete\n"); send "deleteYourself" to thisObject; exit; } int t = getLocalRelativeTime(); int delta = t - lastRequestTime; if (delta > MAX_IDLE_TIME) { display("Connection with ", peerAddress, " timed out\n"); send "deleteYourself" to thisObject; exit; } assoc acl = makeDefaultACL(); timerObj = send "createObject"("TimerEvent", acl, thisObject, MAX_IDLE_TIME, t) to ObjectCreator; } POP3clientConnection:canRead(oid bfr) { string cmd; lastRequestTime = getLocalRelativeTime(); any line = send "readLine" to readBfr; display("POP3 client line=", line, "\n"); if (line == nil) { // EOF display("Unexpected EOF from POP3 client ", peerAddress, "\n"); send "deleteYourself" to readBfr; readBfr = nil; sessionClosed = 1; exit; } any tokens = tokenizeString(line, " \t\r\n", 0); if (elementCount(tokens) > 0) { cmd = convertCase(tokens[0], 0); } else { cmd = ""; // bogus command } if (currentParseState == AUTHORIZATION) { // USER, PASS, or APOP if (cmd == "QUIT") { // can always quit line = makeAsString("+OK ", serverConfig["hostName"], " POP3 server now closing transmission channel\r\n"); send "writeBytes"(line) to connObj from nil; send "deleteYourself" to readBfr; readBfr = nil; sessionClosed = 1; exit; } if (cmd == "USER") { call "_user"(tokens); } else if (cmd == "PASS") { call "_pass"(tokens); // } else if (cmd == "APOP") { // call "_apop"(tokens); } else { send "writeBytes"("-ERR Unrecognized AUTHORIZATION command\r\n") to connObj from nil; } } else if (currentParseState == TRANSACTION) { // STAT, LIST, RETR, DELE, NOOP, RSET if (cmd == "QUIT") { // move to UPDATE state call "_commitDeletes"(); currentParseState = UPDATE; line = makeAsString("+OK ", serverConfig["hostName"], " POP3 server now closing transmission channel\r\n"); send "writeBytes"(line) to connObj from nil; send "deleteYourself" to readBfr; readBfr = nil; sessionClosed = 1; exit; } if (cmd == "RSET") { send "writeBytes"("+OK reset\r\n") to connObj from nil; messagesToDelete = emptySet; messageIdList = send "getMessageIds" to userMailbox; } else if (cmd == "NOOP") { send "writeBytes"("+OK nothing done\r\n") to connObj from nil; } else if (cmd == "DELE") { call "_dele"(tokens); } else if (cmd == "RETR") { call "_retr"(tokens); } else if (cmd == "LIST") { call "_list"(tokens); } else if (cmd == "STAT") { call "_stat"(tokens); } else if (cmd == "UIDL") { call "_uidl"(tokens); } else if (cmd == "TOP") { call "_top"(tokens); } else if (cmd == "XSENDER") { call "_xsender"(tokens); } else { send "writeBytes"("-ERR Unrecognized TRANSACTION command\r\n") to connObj from nil; } } send "selectForRead" to readBfr; } POP3clientConnection:_user(array tokens) { string response; authenticated = 0; userMailbox = send "lookupMailbox"(tokens[1]) to mailboxService; if (userMailbox == nil) { response = makeAsString("-ERR no mailbox for ", tokens[1], "\r\n"); send "writeBytes"(response) to connObj from nil; return (0); } messageIdList = send "getMessageIds" to userMailbox; assoc info = send "getMailboxInfo" to userMailbox; response = makeAsString("+OK have mailbox for ", info["fullName"], "\r\n"); send "writeBytes"(response) to connObj from nil; return (0); } POP3clientConnection:_pass(array tokens) { string response; if (userMailbox == nil) { response = makeAsString("-ERR no mailbox found by last USER command\r\n"); send "writeBytes"(response) to connObj from nil; return (0); } int ok = send "authenticate"(tokens[1]) to userMailbox; if (ok == 0) { response = "-ERR wrong password\r\n"; } else { authenticated = 1; currentParseState = TRANSACTION; response = "+OK valid password\r\n"; } send "writeBytes"(response) to connObj from nil; return (0); } POP3clientConnection:_apop(array tokens) { string response; authenticated = 0; userMailbox = send "lookupMailbox"(tokens[1]) to mailboxService; if (userMailbox == nil) { response = makeAsString("-ERR no mailbox for ", tokens[1], "\r\n"); send "writeBytes"(response) to connObj from nil; return (0); } string md5Digest = tokens[2]; int ok = send "authenticate"(md5Digest) to userMailbox; if (ok == 0) { response = "-ERR wrong password\r\n"; } else { authenticated = 1; currentParseState = TRANSACTION; response = "+OK valid password\r\n"; } messageIdList = send "getMessageIds" to userMailbox; send "writeBytes"(response) to connObj from nil; return (0); } POP3clientConnection:_stat(array tokens) { int totalMessages = 0; int totalSize = 0; int i = nextIndex(messageIdList, 0); while (i != 0) { totalMessages += 1; totalSize += messageIdList[i]; i = nextIndex(messageIdList, i); } string response = makeAsString("+OK ", totalMessages, " ", totalSize, "\r\n"); send "writeBytes"(response) to connObj from nil; return (0); } POP3clientConnection:_list(array tokens) { set lines; string response; if (elementCount(tokens) == 2) { // specific message int id = stringToNumber(tokens[1], int); if (indexExists(messageIdList, id) == 0) { response = "-ERR no such message\r\n"; } else { response = makeAsString("+OK ", id, " ", messageIdList[id], "\r\n"); } send "writeBytes"(response) to connObj from nil; return (0); } // dump everything int i = nextIndex(messageIdList, 0); while (i != 0) { lines += makeAsString(i, " ", messageIdList[i], "\r\n"); i = nextIndex(messageIdList, i); } response = makeAsString("+OK\r\n", lines, ".\r\n"); send "writeBytes"(response) to connObj from nil; return (0); } POP3clientConnection:_dele(array tokens) { string response; int id = stringToNumber(tokens[1], int); if (indexExists(messageIdList, id) == 0) { response = "-ERR no such message\r\n"; } else { response = "+OK message deleted\r\n"; messagesToDelete += id; messageIdList = deleteIndex(messageIdList, id); } send "writeBytes"(response) to connObj from nil; return (0); } POP3clientConnection:_retr(array tokens) { string response; int id = stringToNumber(tokens[1], int); if (indexExists(messageIdList, id) == 0) { response = "-ERR no such message\r\n"; send "writeBytes"(response) to connObj from nil; return (0); } any mess = send "getMessage"(id) to userMailbox; response = "+OK message below\r\n"; send "writeVectorOfBytes"(0, response, mess, ".\r\n") to connObj from nil; return (0); } POP3clientConnection:_top(array tokens) { int lineCount; string response; int id = stringToNumber(tokens[1], int); if (indexExists(messageIdList, id) == 0) { response = "-ERR no such message\r\n"; send "writeBytes"(response) to connObj from nil; return (0); } lineCount = stringToNumber(tokens[2], int); any mess = send "getMessageHeader"(id, lineCount) to userMailbox; response = "+OK message header below\r\n"; send "writeVectorOfBytes"(0, response, mess, ".\r\n") to connObj from nil; return (0); } POP3clientConnection:_xsender(array tokens) { string response; int id = stringToNumber(tokens[1], int); if (indexExists(messageIdList, id) == 0) { response = "-ERR no such message\r\n"; send "writeBytes"(response) to connObj from nil; return (0); } response = ""; send "writeVectorOfBytes"(0, "+OK ", response, "\r\n") to connObj from nil; return (0); } POP3clientConnection:_uidl(array tokens) { set lines; string mess, response; if (elementCount(tokens) == 2) { // specific message int id = stringToNumber(tokens[1], int); if (indexExists(messageIdList, id) == 0) { response = "-ERR no such message\r\n"; } else { mess = send "getUniqueMessageId"(id) to userMailbox; response = makeAsString("+OK ", id, " ", mess, "\r\n"); } send "writeBytes"(response) to connObj from nil; return (0); } // dump everything int i = nextIndex(messageIdList, 0); while (i != 0) { mess = send "getUniqueMessageId"(i) to userMailbox; lines += makeAsString(i, " ", mess, "\r\n"); i = nextIndex(messageIdList, i); } response = makeAsString("+OK\r\n", lines, ".\r\n"); send "writeBytes"(response) to connObj from nil; return (0); } POP3clientConnection:_commitDeletes() { if (elementCount(messagesToDelete) > 0) { send "deleteMessages"(messagesToDelete) to userMailbox from nil; messagesToDelete = emptySet; messageIdList = send "getMessageIds" to userMailbox; } return (0); } // Not used by POP3 spec POP3clientConnection:systemShutdown() { string line = makeAsString("-ERR ", serverConfig["hostName"], " is shutting down\r\n"); int rc = send "writeBytes"(line) to connObj; send "deleteYourself" to readBfr; readBfr = nil; sessionClosed = 1; shutdownOID = nil; } /* vim: set expandtab shiftwidth=4 tabstop=4: */