// This file is encoded in UTF-8. // // This is generic code for talking to an email server. // Copyright 2003 Adobe Systems Inc. // Create an email object. The function may be called both // as a global function and as a constructor. It takes the // name of the email server, and an optional Boolean that, // if true, prints debugging messages. // This object is not guaranteed to work for all SMTP servers, // some of them may require a different set of commands. // functions: // send (fromAddress, toAddress, subject, text) - send an email // auth (name, pass) - do an authorization via POP3 // both functions return false on errors // sample: // e = new EmailSocket ("mail.host.com"); // authorize via POP3 (not all servers require authorization) // e.auth ("myname", "mypass"); // send the email // e.send ("me@my.com", "you@you.com", "My Subject", "Hi there!") // This script makes use of the Socket object, and creates a new class // called EmailSocket that is derived from Socket. For more information on // creating new classes in this way, consult chapter 7 of JavaScript, The // Definitive Guide, by David Flanagan (O'Reilly). // // Version History: // V1 initial release // v2 - fix problems with bare linefeeds in the email that // caused it to be rejected by some servers. http://cr.yp.to/docs/smtplf.html // v3 - add support for the SMTP AUTH command. Only the LOGIN // protocol is supported since it looks like all servers support // it, while not all support PLAIN. Outlook Express 4.x and 5.x use // LOGIN as there AUTH protocol. // Also fix bug where EHLO sent a syntactically incorrect argument //This is the constructor for the email socket. It takes as arguments: //server - the address of the email server (is not checked for validity here) //dbg - a boolean, if true, prints additional error information function EmailSocket (server, dbg) { var obj = new Socket; obj._host = server; obj._debug = (dbg == true); obj.__proto__ = EmailSocket.prototype; obj.headers = new Array(); return obj; } // correct the protoype chain to point to the Socket prototype chain // - this is what actually causes the derivation from Socket. EmailSocket.prototype.__proto__ = Socket.prototype; // This sets up the send() member function. send() takes as arguments: // from - the email address of the sender. This is not validated. // to - the email address of the recipient. If there is an error, // and the from address is incorrect, you will not be notified. // subject - the contents of the subject field. // text - the body of the message. // auth_user - OPTIONAL the username of the account to authorize with the smtp server // auth_password - OPTIONAL - the already base64 encoded password // // Returns: // true if sending succeeded // THROWS AN ERROR STRING if the function fails. // // // Note that this code uses a local function object to create // the function that is assigned to send. EmailSocket.prototype.send = function (from, to, subject, text, auth_user, auth_password) { // open the socket on port 25 (SMTP) // right now this is binary because otherwise the CRLF got reinterpreted and // my server would not accept it. We will need changes to the socket object // to fix correctly. if (!this.open (this._host + ":25", "binary")) return false; try { // discard the greeting var greeting = this.read(); if (this._debug) write ("RECV: " + greeting); // issue EHLO and other commands // one user is having aproblem with the full email as the @ sign is illegal // we should use the ip of the client http://cr.yp.to/smtp/helo.html#helo // but we can't get that with the socket object. therefore we are using // the host portion of the reply to email address. var client_only = from.split("@")[1]; this._SMTP ("EHLO " + client_only); var response = this.read(); if (auth_user) { // now send the auth stuff this._SMTP("AUTH LOGIN"); this._SMTP(this._ENCODE_BASE_64(auth_user)); this._SMTP(auth_password); } this._SMTP ("MAIL FROM: " + from); this._SMTP ("RCPT TO: " + to); this._SMTP ("DATA"); // send subject and time stamp this.writecrlfln ("From: " + from); this.writecrlfln ("To: " + to); this.writecrlfln ("Date: " + new Date().toString()); for( key in this.headers) { this.writecrlfln (key + ": " + this.headers[key]); } if (typeof subject != undefined) this.writecrlfln ("Subject: " + subject); this.writecrlfln(); // send the text if (typeof text != undefined) this.writecrlfln (text); // terminate with a single dot and wait for response this._SMTP ("."); // terminate the session this._SMTP ("QUIT"); this.close(); return true; } catch (e) { this.close(); throw e; } } // Authorize via POP3. Supply name and password. // // Returns: // true if sending succeeded // false otherwise (if there was an error) // // Arguments: // name - the userName of the account // pass - the password EmailSocket.prototype.auth = function (name, pass) { // open the connection on port 110 (POP3) if (!this.open (this._host + ":110")) return false; try { // discard the greeting var greeting = this.read(); if (this._debug) write ("RECV: " + greeting); // issue POP3 commands this._POP3 ("USER " + name); this._POP3 ("PASS " + pass); this._POP3 ("QUIT"); this.close(); return true; } catch (e) { this.close(); return false; } } // Users of the EmailSocket do not need to be concerned with // the following method. It is an implementation helper. // local function to send a command & check a POP3 reply // throws in case of error EmailSocket.prototype._POP3 = function (cmd) { if (this._debug) writeLn ("SEND: " + cmd); if (!this.writecrlfln (cmd)) throw "Error"; var reply = this.read(); if (this._debug) write ("RECV: " + reply); // the reply starts by either + or - if (reply [0] == "+") return; throw "Error"; } // Users of the EmailSocket do not need to be concerned with // the following method. It is an implementation helper. // local function to send a command & check a SMTP reply // throws in case of error EmailSocket.prototype._SMTP = function (cmd) { if (this._debug) writeLn ("SEND: " + cmd); if (!this.writecrlfln (cmd)) throw "Error"; var reply = this.read(); if (this._debug) write ("RECV: " + reply); // the reply is a three-digit code followed by a space var match = reply.match (/^(\d{3})\s/m); if (match.length == 2) { var n = Number (match [1]); if (n >= 200 && n <= 399) return; } throw reply; } // email can't contain bare linefeeds: http://cr.yp.to/docs/smtplf.html EmailSocket.prototype.writecrlfln = function (cmd) { if (cmd) { return this.write(cmd + "\r\n"); } else { return this.write("\r\n"); } } // This base64 encodes str. RFC-2045 EmailSocket.prototype._ENCODE_BASE_64 = function(str) { var lut = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var chunk = 0; var result = ""; for( var i = 0; i < str.length; i += 3) { chunk = str.charCodeAt(i) & 0xFF; chunk = chunk << 8; // asking for a char out of range returns "" chunk |= str.charCodeAt(i+1) & 0xFF; chunk = chunk << 8; chunk |= str.charCodeAt(i+2) & 0xFF; // these are the number of gaps until we hit %3 var partialEnd = 3 - (str.length - i) ; var num6bits = 4; // if (partialEnd == 2) { // one character encoded num6bits = 2; } else if (partialEnd == 1) { // two encoded num6bits = 3; } for( var j = 0; j < num6bits; j++){ result += lut.charAt((chunk & 0xFC0000) >> 18); // six bits chunk = chunk << 6; } // fill with = chars if (partialEnd < 3) { for( var j = 0; j < partialEnd; j++){ result += "="; } } } return result; } // nice to have: a toString() // This function allows the email object to be printed. EmailSocket.prototype.toString = function() { return "[object Email]"; }