Clean up some compile errors on Debian Etch.
[inoclam.git] / src / mailer.cxx
blobd4dfd9c496e4bd111285e68dcac496b740b95255
1 // Note that the only valid version of the GPL as far as jwSMTP
2 // is concerned is v2 of the license (ie v2, not v2.2 or v3.x or whatever),
3 // unless explicitly otherwise stated.
4 //
5 // This file is part of the jwSMTP library.
6 //
7 // jwSMTP library is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; version 2 of the License.
11 // jwSMTP library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with jwSMTP library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 // jwSMTP library
21 // http://johnwiggins.net
22 // smtplib@johnwiggins.net
24 // Modified by Tom Cort <tom.cort@state.vt.us 30-Oct-2008.
25 // Renamed headers to .hxx and removed WIN32 specific code.
27 // Modified by Tom Cort <tom.cort@state.vt.us 27-Feb-2009.
28 // include string.h (compile fix) and case literal to char*.
30 #include <fstream>
31 #include <sstream> // ostrstream
32 #include <ctime> // for localtime
33 #include <cassert>
34 #include <string.h>
35 #include "mailer.hxx"
36 #include "base64.hxx"
38 namespace jwsmtp {
40 mailer::mailer(const char* TOaddress, const char* FROMaddress,
41 const char* Subject, const std::vector<char>& Message,
42 const char* Nameserver, unsigned short Port,
43 bool MXLookup): type(LOGIN),
44 subject(Subject),
45 server(getserveraddress(TOaddress)),
46 nameserver(Nameserver),
47 port(htons(Port)), // make the 'port' network byte order.
48 lookupMXRecord(MXLookup),
49 auth(false) {
50 // Parse the email addresses into an Address structure.
51 setsender(FROMaddress);
52 addrecipient(TOaddress);
53 setmessage(Message);
57 mailer::mailer(const char* TOaddress, const char* FROMaddress,
58 const char* Subject, const char* Message,
59 const char* Nameserver, unsigned short Port,
60 bool MXLookup): type(LOGIN),
61 subject(Subject),
62 server(getserveraddress(TOaddress)),
63 nameserver(Nameserver),
64 port(htons(Port)), // make the 'port' network byte order.
65 lookupMXRecord(MXLookup),
66 auth(false) {
67 // Parse the email addresses into an Address structure.
68 setsender(FROMaddress);
69 addrecipient(TOaddress);
70 setmessage(Message);
74 mailer::mailer(bool MXLookup, unsigned short Port):
75 type(LOGIN),
76 port(htons(Port)),
77 lookupMXRecord(MXLookup),
78 auth(false) {
81 mailer::~mailer() { }
83 bool mailer::setmessage(const std::string& newmessage) {
84 if(!newmessage.length())
85 return false;
87 message.clear(); // erase the old message
88 for (std::string::size_type i = 0; i < newmessage.length(); ++i)
89 message.push_back(newmessage[i]);
91 checkRFCcompat();
93 return true;
96 bool mailer::setmessage(const std::vector<char>& newmessage) {
97 if(!newmessage.size())
98 return false;
100 message = newmessage;
102 checkRFCcompat();
104 return true;
107 bool mailer::setmessageHTML(const std::string& newmessage) {
108 if(!newmessage.length())
109 return false;
111 messageHTML.clear(); // erase the old message
112 for (std::string::size_type i = 0; i < newmessage.length(); ++i)
113 messageHTML.push_back(newmessage[i]);
114 messageHTML = base64encode(messageHTML);
116 return true;
119 bool mailer::setmessageHTML(const std::vector<char>& newmessage) {
120 if(!newmessage.size())
121 return false;
123 messageHTML = base64encode(newmessage);
125 return true;
128 bool mailer::setmessageHTMLfile(const std::string& filename) {
129 if(!filename.length())
130 return false;
132 std::ifstream file(filename.c_str(), std::ios::binary | std::ios::in);
133 if(!file)
134 return false;
135 std::vector<char> filedata;
136 char c = file.get();
137 for(; file.good(); c = file.get()) {
138 if(file.bad())
139 break;
140 filedata.push_back(c);
143 messageHTML = base64encode(filedata);
145 return true;
148 // this breaks a message line up to be less than 1000 chars per line.
149 // keeps words intact also.
150 // Check line returns are in the form "\r\n"
151 // (qmail balks otherwise, i.e. LAME server)
152 void mailer::checkRFCcompat() {
153 // Check the line breaks.
154 std::vector<char>::iterator it;
155 for(it = message.begin(); it != message.end(); ++it) {
156 // look for \n add \r before if not there. Pretty lame but still.
157 // haven't thought of a better way yet.
158 if(*it == '\n') {
159 if(it == message.begin()) {
160 it = message.insert(it, '\r');
161 ++it; // step past newline
162 continue;
164 if((*(it -1) != '\r') ) {
165 // add a return before '\n'
166 it = message.insert(it, '\r');
167 ++it; // step past newline
172 // if we get a period on a line by itself
173 // add another period to stop the server ending the mail prematurely.
174 // ( suggested by david Irwin )
175 if(message.size() == 1) {
176 if(*(message.begin()) == '.')
177 message.push_back('.');
179 else if(message.size() == 2) {
180 if(*(message.begin()) == '.') {
181 it = message.begin();
182 it = message.insert(it, '.');
185 else {
186 if(*(message.begin()) == '.') {
187 it = message.begin();
188 it = message.insert(it, '.');
190 for(it = message.begin()+2; it != message.end(); ++it) {
191 // follow the rfc. Add '.' if the first character on a line is '.'
192 if(*it == '\n') {
193 if( ((it + 1) != message.end()) && (*(it +1) == '.') ) {
194 it = message.insert(it + 1, '.');
195 ++it; // step past
201 // don't do anything if we are not longer than a 1000 characters
202 if(message.size() < 1000)
203 return;
205 // now we have checked line breaks
206 // check line lengths.
207 int count(1);
208 for(it = message.begin(); it < message.end(); ++it, ++count) {
209 if(*it == '\r') {
210 count = 0; // reset for a new line.
211 ++it; // get past newline
212 continue;
214 else if(count >= 998) {
215 ++it;
216 if(*it != ' ') { // we are not in a word!!
217 // it should never get to message.begin() because we
218 // start at least 998 chars into the message!
219 // Also, assume a word isn't bigger than 997 chars! (seems reasonable)
220 std::vector<char>::iterator pos = it;
221 for(int j = 0; j < 997; ++j, --pos) {
222 if(*pos == ' ') {
223 it = ++pos; // get past the space.
224 break;
228 if(it < message.end())
229 it = message.insert(it, '\r');
230 ++it;
231 if(it < message.end())
232 it = message.insert(it, '\n');
233 count = 0; // reset for a new line.
236 count=1; // reset the count
237 if(messageHTML.size()) {
238 for(it = messageHTML.begin(); it < messageHTML.end(); ++it, ++count) {
239 if(*it == '\r') {
240 count = 0; // reset for a new line.
241 ++it; // get past newline
242 continue;
244 else if(count >= 998) {
245 ++it;
246 if(*it != ' ') { // we are in a word!!
247 // it should never get to message.begin() because we
248 // start at least 998 chars into the message!
249 // Also, assume a word isn't bigger than 997 chars! (seems reasonable)
250 std::vector<char>::iterator pos = it;
251 for(int j = 0; j < 997; ++j, --pos) {
252 if(*pos == ' ') {
253 it = ++pos; // get past the space.
254 break;
258 if(it < messageHTML.end())
259 it = messageHTML.insert(it, '\r');
260 ++it;
261 if(it < messageHTML.end())
262 it = messageHTML.insert(it, '\n');
263 count = 0; // reset for a new line.
269 bool mailer::setsubject(const std::string& newSubject) {
270 if(!newSubject.length())
271 return false;
273 subject = newSubject;
274 return true;
277 bool mailer::setserver(const std::string& nameserver_or_smtpserver) {
278 if(!nameserver_or_smtpserver.length())
279 return false;
281 nameserver = nameserver_or_smtpserver;
282 return true;
285 bool mailer::setsender(const std::string& newsender) {
286 if(!newsender.length())
287 return false;
289 Address newaddress(parseaddress(newsender));
291 fromAddress = newaddress;
292 return true;
295 bool mailer::addrecipient(const std::string& newrecipient, short recipient_type) {
296 // SMTP only allows 100 recipients max at a time.
297 // rfc821
298 if(recipients.size() >= 100) // == would be fine, but let's be stupid safe
299 return false;
301 if(newrecipient.length()) {
302 // If there are no recipients yet
303 // set the server address for MX queries
304 if(!recipients.size()) {
305 server = getserveraddress(newrecipient);
308 Address newaddress = parseaddress(newrecipient);
310 if(recipient_type > Bcc || recipient_type < TO)
311 recipient_type = Bcc; // default to blind copy on error(hidden is better)
313 recipients.push_back(std::make_pair(newaddress, recipient_type));
314 return true;
316 return false;
319 bool mailer::removerecipient(const std::string& recipient) {
320 if(recipient.length()) { // there is something to remove
321 std::vector<std::pair<Address, short> >::iterator it(recipients.begin());
322 for(; it < recipients.end(); ++it) {
323 if((*it).first.address == recipient) {
324 recipients.erase(it);
325 return true;
328 // fall through as we did not find this recipient
330 return false;
333 void mailer::clearrecipients() {
334 recipients.clear();
337 void mailer::clearattachments() {
338 attachments.clear();
341 void mailer::reset() {
342 recipients.clear();
343 attachments.clear();
344 // fromAddress = ""; // assume the same sender.
345 // if this is to be changed use the setserver function to change it.
346 // nameserver = ""; // we don't do this as the same server is probably used!!
347 // leave auth type alone.
348 // leave username password pair alone.
349 server = "";
350 message.clear();
351 messageHTML.clear();
352 returnstring = ""; // clear out any errors from previous use
355 // convenience function
356 void mailer::send() {
357 operator()();
360 // this is where we do all the work.
361 void mailer::operator()() {
362 returnstring = ""; // clear out any errors from previous use
364 if(!recipients.size()) {
365 returnstring = "451 Requested action aborted: local error who am I mailing";
366 return;
368 if(!fromAddress.address.length()) {
369 returnstring = "451 Requested action aborted: local error who am I";
370 return;
372 if(!nameserver.length()) {
373 returnstring = "451 Requested action aborted: local error no SMTP/name server/smtp server";
374 return;
377 std::vector<SOCKADDR_IN> adds;
378 if(lookupMXRecord) {
379 if(!gethostaddresses(adds)) {
380 // error!! we are dead.
381 returnstring = "451 Requested action aborted: No MX records ascertained";
382 return;
385 else { // connect directly to an SMTP server.
386 SOCKADDR_IN addr(nameserver, port, AF_INET);
387 hostent* host = 0;
388 if(addr) {
389 host = gethostbyaddr(addr.get_sin_addr(), sizeof(addr.ADDR.sin_addr), AF_INET);
391 else
392 host = gethostbyname(nameserver.c_str());
393 if(!host) {
394 returnstring = "451 Requested action aborted: local error in processing";
395 return; // error!!!
397 //memcpy(addr.get_sin_addr(), host->h_addr, host->h_length);
398 std::copy(host->h_addr_list[0], host->h_addr_list[0] + host->h_length, addr.get_sin_addr());
399 adds.push_back(addr);
402 SOCKET s;
403 if(!Socket(s, AF_INET, SOCK_STREAM, 0)) {
404 returnstring = "451 Requested action aborted: socket function error";
405 return;
408 if(!adds.size()) { // oops
409 returnstring = "451 Requested action aborted: No MX records ascertained";
412 const std::string OK("250");
413 const std::vector<char> smtpheader(makesmtpmessage());
414 const int buffsize(1024);
415 char buff[buffsize] = "";
417 for(std::vector<SOCKADDR_IN>::const_iterator address = adds.begin();
418 address < adds.end(); ++address) {
419 if(!Connect(s, *address)) {
420 returnstring = "554 Transaction failed: server connect error.";
421 continue;
424 // 220 the server line returned here
425 int len1;
426 if(!Recv(len1, s, buff, buffsize -1, 0)) {
427 returnstring = "554 Transaction failed: server connect response error.";
428 continue;
431 // get our hostname to pass to the smtp server
432 char hn[buffsize] = "";
433 if(gethostname(hn, buffsize)) {
434 // no local hostname!!! make one up
435 strcpy(hn, "flibbletoot");
437 std::string commandline(std::string("EHLO ") + hn + std::string("\r\n"));
438 // say hello to the server
440 if(!Send(len1, s, commandline.c_str(), commandline.length(), 0)) {
441 returnstring = "554 Transaction failed: EHLO send";
442 continue;
444 if(!Recv(len1, s, buff, buffsize -1, 0)) {
445 returnstring = "554 Transaction failed: EHLO receipt";
446 continue;
449 buff[len1] = '\0';
450 std::string greeting = returnstring = buff;
451 if(returnstring.substr(0,3) != OK) {
452 if(auth) {
453 // oops no ESMTP but using authentication no go bail out!
454 returnstring = "554 possibly trying to use AUTH without ESMTP server, ERROR!";
455 continue;
457 // maybe we only do non extended smtp
458 // send HELO instead.
459 commandline[0] = 'H';
460 commandline[1] = 'E';
461 if(!Send(len1, s, commandline.c_str(), commandline.length(), 0)) {
462 returnstring = "554 Transaction failed: HELO send";
463 continue;
465 if(!Recv(len1, s, buff, buffsize -1, 0)) {
466 returnstring = "554 Transaction failed: HELO receipt";
467 continue;
469 buff[len1] = '\0';
471 returnstring = buff;
472 if(returnstring.substr(0,3) != OK) {
473 // we must issue a quit even on an error.
474 // in keeping with the rfc's
475 if(Send(len1, s, "QUIT\r\n", 6, 0)) {
476 char dummy[buffsize];
477 Recv(len1, s, dummy, buffsize -1, 0);
479 Closesocket(s);
480 // don't know what went wrong here if we are connected!!
481 // we continue because maybe we have more than 1 server to connect to.
482 continue;
486 if(auth)
487 if(!authenticate(greeting, s))
488 continue; // try the next server, you never know!!
490 // MAIL
491 // S: MAIL FROM:<Smith@Alpha.ARPA>
492 // R: 250 OK
493 // e.g. "MAIL FROM:<someone@somewhere.com>\r\n"
494 // or "MAIL FROM: John Wiggins <someone@somewhere.com>"
495 commandline = "MAIL FROM:<" + fromAddress.address + ">\r\n";
496 if(!Send(len1, s, commandline.c_str(), commandline.length(), 0)) {
497 returnstring = "554 MAIL FROM sending error";
498 continue;
501 if(!Recv(len1, s, buff, buffsize -1, 0)) {
502 returnstring = "554 MAIL FROM receipt error";
503 continue;
506 buff[len1] = '\0';
507 returnstring = buff;
508 if(returnstring.substr(0,3) != OK) {
509 // we must issue a quit even on an error.
510 // in keeping with the rfc's
511 if(Send(len1, s, "QUIT\r\n", 6, 0)) {
512 char dummy[buffsize];
513 Recv(len1, s, dummy, buffsize -1, 0);
515 Closesocket(s);
516 // don't know what went wrong here if we are connected!!
517 // we continue because maybe we have more than 1 server to connect to.
518 continue;
521 for(recipient_const_iter recip = recipients.begin(); recip < recipients.end(); ++recip) {
522 // RCPT
524 // S: RCPT TO:<Jones@Beta.ARPA>
525 // R: 250 OK
526 commandline = "RCPT TO: <" + (*recip).first.address + ">\r\n";
527 // S: RCPT TO:<Green@Beta.ARPA>
528 // R: 550 No such user here
530 // S: RCPT TO:<Brown@Beta.ARPA>
531 // R: 250 OK
532 if(!Send(len1, s, commandline.c_str(), commandline.length(), 0)) {
533 returnstring = "554 Transaction failed";
534 continue;
536 if(!Recv(len1, s, buff, buffsize -1, 0)) {
537 returnstring = buff;
538 continue;
540 buff[len1] = '\0';
541 returnstring = buff;
542 if(returnstring.substr(0,3) != OK) {
543 // This particular recipient does not exist!
544 // not strictly an error as we may have more than one recipient
545 // we should have an error vector e.g.
546 // vector<pair<string address, string error> > errs;
547 // errs.push_back(make_pair(recip->first, returnstring));
549 // we then need a function to return this vector.
550 // e.g. const vector<pair<string address, string error> >& getrecipienterrors();
551 continue;
555 // DATA
557 // S: DATA
558 // R: 354 Start mail input; end with <CRLF>.<CRLF>
559 // S: Blah blah blah...
560 // S: ...etc. etc. etc.
562 // S: <CRLF>.<CRLF>
563 // R: 250 OK
564 if(!Send(len1, s, "DATA\r\n", 6, 0)) {
565 returnstring = "554 DATA send error";
566 continue;
568 if(!Recv(len1, s, buff, buffsize -1, 0)) {
569 returnstring = "554 DATA, server response error";
570 continue;
572 buff[len1] = '\0';
573 returnstring = buff;
574 if(returnstring.substr(0,3) != "354") {
575 // we must issue a quit even on an error.
576 // in keeping with the rfc's
578 if(Send(len1, s, "QUIT\r\n", 6, 0)) {
579 char dummy[buffsize];
580 Recv(len1, s, dummy, buffsize -1, 0);
582 Closesocket(s);
583 continue;
585 // Sending the email
586 /*if(!Send(len1, s, smtpheader.c_str(), smtpheader.length(), 0)) {*/
587 if(!Send(len1, s, &smtpheader[0], smtpheader.size(), 0)) {
588 returnstring = "554 DATA, server response error (actual send)";
589 continue;
591 if(!Recv(len1, s, buff, buffsize -1, 0)) {
592 returnstring = "554 DATA, server response error (actual send)";
593 continue;
596 // The server should give us a 250 reply if the mail was delivered okay
597 buff[len1] = '\0';
598 returnstring = buff;
599 if(returnstring.substr(0,3) != OK) {
600 // we must issue a quit even on an error.
601 // in keeping with the rfc's
602 if(Send(len1, s, "QUIT\r\n", 6, 0)) {
603 char dummy[buffsize];
604 Recv(len1, s, dummy, buffsize -1, 0);
606 Closesocket(s);
607 continue;
609 // hang up the connection
610 if(Send(len1, s, "QUIT\r\n", 6, 0)) {
611 char dummy[buffsize];
612 Recv(len1, s, dummy, buffsize -1, 0);
615 // Let the server give us our 250 reply.
616 //buff[len1] = '\0';
617 //returnstring = buff;
619 // for future reference the server is meant to give a 221 response to a quit.
620 if(returnstring.substr(0,3) != "221") {
621 // maybe will use this later
623 Closesocket(s); // disconnect
625 // Let the server give us our 250 reply.
626 // don't continue as we have delivered the mail
627 // at this point just leave. all done
628 //returnstring = "250 Requested mail action okay, completed";
629 break;
633 std::vector<char> mailer::makesmtpmessage() const {
634 std::string sender(fromAddress.address);
635 if(sender.length()) {
636 std::string::size_type pos(sender.find("@"));
637 if(pos != std::string::npos) { //found the server beginning
638 sender = sender.substr(0, pos);
642 std::vector<char> ret;
643 std::string headerline;
644 if(fromAddress.name.length()) {
645 headerline = "From: " + fromAddress.address + " (" + fromAddress.name + ") \r\n"
646 "Reply-To: " + fromAddress.address + "\r\n";
647 ret.insert(ret.end(), headerline.begin(), headerline.end());
649 else {
650 headerline = "From: " + fromAddress.address + "\r\n"
651 "Reply-To: " + fromAddress.address + "\r\n";
652 ret.insert(ret.end(), headerline.begin(), headerline.end());
654 headerline.clear(); // clearout our temp variable
656 // add the recipients to the header
657 std::vector<std::string> to, cc, bcc;
658 for(recipient_const_iter recip = recipients.begin(); recip < recipients.end(); ++recip) {
659 if(recip->second == TO) {
660 to.push_back(recip->first.address);
662 else if(recip->second == Cc) {
663 cc.push_back(recip->first.address);
665 else if(recip->second == Bcc) {
666 bcc.push_back(recip->first.address);
669 vec_str_const_iter it; // instead of making three on the stack, just one (stops VC whining too)
670 // next section adds To: Cc: Bcc: lines to the header
671 int count = to.size();
672 if(count)
673 headerline += "To: ";
674 for(it = to.begin(); it < to.end(); ++it) {
675 headerline += *it;
676 if(count > 1 && ((it + 1) < to.end()) )
677 headerline += ",\r\n "; // must add a space after the comma
678 else
679 headerline += "\r\n";
681 count = cc.size();
682 if(count)
683 headerline += "Cc: ";
684 for(it = cc.begin(); it < cc.end(); ++it) {
685 headerline += *it;
686 if(count > 1 && ((it + 1) < cc.end()) )
687 headerline += ",\r\n "; // must add a space after the comma
688 else
689 headerline += "\r\n";
692 // Remove insertion of the Bcc line into the header, I should never have done this, oopsy
694 //count = bcc.size();
695 //if(count)
696 // headerline += "Bcc: ";
697 //for(it = bcc.begin(); it < bcc.end(); ++it) {
698 // headerline += *it;
699 // if(count > 1 && ((it + 1) < bcc.end()) )
700 // headerline += ",\r\n "; // must add a space after the comma
701 // else
702 // headerline += "\r\n";
704 ret.insert(ret.end(), headerline.begin(), headerline.end());
705 // end adding To: Cc: Bcc: lines to the header
707 const std::string boundary("bounds=_NextP_0056wi_0_8_ty789432_tp");
708 bool MIME(false);
709 if(attachments.size() || messageHTML.size())
710 MIME = true;
712 if(MIME) { // we have attachments
713 // use MIME 1.0
714 headerline = "MIME-Version: 1.0\r\n"
715 "Content-Type: multipart/mixed;\r\n"
716 "\tboundary=\"" + boundary + "\"\r\n";
717 ret.insert(ret.end(), headerline.begin(), headerline.end());
718 headerline.clear();
721 ///////////////////////////////////////////////////////////////////////////
722 // add the current time.
723 // format is
724 // Date: 05 Jan 93 21:22:07
725 // Date: 05 Jan 93 21:22:07 -0500
726 // Date: 27 Oct 81 15:01:01 PST (RFC 821 example)
727 time_t t;
728 time(&t);
729 char timestring[128] = "";
730 char * timeformat = (char*)"Date: %d %b %y %H:%M:%S %Z";
731 if(strftime(timestring, 127, timeformat, localtime(&t))) { // got the date
732 headerline = timestring;
733 headerline += "\r\n";
734 ret.insert(ret.end(), headerline.begin(), headerline.end());
736 ///////////////////////////////////////////////////////////////////////////
737 // add the subject
738 headerline = "Subject: " + subject + "\r\n\r\n";
739 ret.insert(ret.end(), headerline.begin(), headerline.end());
741 ///////////////////////////////////////////////////////////////////////////
743 // everything else added is the body of the email message.
745 ///////////////////////////////////////////////////////////////////////////
747 if(MIME) {
748 headerline = "This is a MIME encapsulated message\r\n\r\n";
749 headerline += "--" + boundary + "\r\n";
750 if(!messageHTML.size()) {
751 // plain text message first.
752 headerline += "Content-type: text/plain; charset=iso-8859-1\r\n"
753 "Content-transfer-encoding: 7BIT\r\n\r\n";
755 ret.insert(ret.end(), headerline.begin(), headerline.end());
756 ret.insert(ret.end(), message.begin(), message.end());
757 headerline = "\r\n\r\n--" + boundary + "\r\n";
759 else { // make it multipart/alternative as we have html
760 const std::string innerboundary("inner_jfd_0078hj_0_8_part_tp");
761 headerline += "Content-Type: multipart/alternative;\r\n"
762 "\tboundary=\"" + innerboundary + "\"\r\n";
764 // need the inner boundary starter.
765 headerline += "\r\n\r\n--" + innerboundary + "\r\n";
767 // plain text message first.
768 headerline += "Content-type: text/plain; charset=iso-8859-1\r\n"
769 "Content-transfer-encoding: 7BIT\r\n\r\n";
770 ret.insert(ret.end(), headerline.begin(), headerline.end());
771 ret.insert(ret.end(), message.begin(), message.end());
772 headerline = "\r\n\r\n--" + innerboundary + "\r\n";
773 ///////////////////////////////////
774 // Add html message here!
775 headerline += "Content-type: text/html; charset=iso-8859-1\r\n"
776 "Content-Transfer-Encoding: base64\r\n\r\n";
778 ret.insert(ret.end(), headerline.begin(), headerline.end());
779 ret.insert(ret.end(), messageHTML.begin(), messageHTML.end());
780 headerline = "\r\n\r\n--" + innerboundary + "--\r\n";
782 // end the boundaries if there are no attachments
783 if(!attachments.size())
784 headerline += "\r\n--" + boundary + "--\r\n";
785 else
786 headerline += "\r\n--" + boundary + "\r\n";
787 ///////////////////////////////////
789 ret.insert(ret.end(), headerline.begin(), headerline.end());
790 headerline.clear();
792 // now add each attachment.
793 for(vec_pair_char_str_const_iter it1 = attachments.begin();
794 it1 != attachments.end(); ++ it1) {
795 if(it1->second.length() > 3) { // long enough for an extension
796 std::string typ(it1->second.substr(it1->second.length()-4, 4));
797 if(typ == ".gif") { // gif format presumably
798 headerline += "Content-Type: image/gif;\r\n";
800 else if(typ == ".jpg" || typ == "jpeg") { // j-peg format presumably
801 headerline += "Content-Type: image/jpg;\r\n";
803 else if(typ == ".txt") { // text format presumably
804 headerline += "Content-Type: plain/txt;\r\n";
806 else if(typ == ".bmp") { // windows bitmap format presumably
807 headerline += "Content-Type: image/bmp;\r\n";
809 else if(typ == ".htm" || typ == "html") { // hypertext format presumably
810 headerline += "Content-Type: plain/htm;\r\n";
812 else if(typ == ".png") { // portable network graphic format presumably
813 headerline += "Content-Type: image/png;\r\n";
815 else if(typ == ".exe") { // application
816 headerline += "Content-Type: application/X-exectype-1;\r\n";
818 else { // add other types
819 // everything else
820 headerline += "Content-Type: application/X-other-1;\r\n";
823 else {
824 // default to don't know
825 headerline += "Content-Type: application/X-other-1;\r\n";
828 headerline += "\tname=\"" + it1->second + "\"\r\n";
829 headerline += "Content-Transfer-Encoding: base64\r\n";
830 headerline += "Content-Disposition: attachment; filename=\"" + it1->second + "\"\r\n\r\n";
831 ret.insert(ret.end(), headerline.begin(), headerline.end());
832 headerline.clear();
834 ret.insert(ret.end(), it1->first.begin(), it1->first.end());
836 // terminate the message with the boundary + "--"
837 if((it1 + 1) == attachments.end())
838 headerline += "\r\n\r\n--" + boundary + "--\r\n";
839 else
840 headerline += "\r\n\r\n--" + boundary + "\r\n";
841 ret.insert(ret.end(), headerline.begin(), headerline.end());
842 headerline.clear();
845 else // just a plain text message only
846 ret.insert(ret.end(), message.begin(), message.end());
848 // end the data in the message.
849 headerline = "\r\n.\r\n";
850 ret.insert(ret.end(), headerline.begin(), headerline.end());
852 return ret;
855 bool mailer::attach(const std::string& filename) {
856 if(!filename.length()) // do silly checks.
857 return false;
859 std::ifstream file(filename.c_str(), std::ios::binary | std::ios::in);
860 if(!file)
861 return false;
863 std::vector<char> filedata;
864 char c = file.get();
865 for(; file.good(); c = file.get()) {
866 if(file.bad())
867 break;
868 filedata.push_back(c);
871 filedata = base64encode(filedata);
873 std::string fn(filename);
874 std::string::size_type p = fn.find_last_of('/');
875 if(p == std::string::npos)
876 p = fn.find_last_of('\\');
877 if(p != std::string::npos) {
878 p +=1; // get past folder delimeter
879 fn = fn.substr(p, fn.length() - p);
882 attachments.push_back(std::make_pair(filedata, fn));
884 return true;
887 bool mailer::removeattachment(const std::string& filename) {
888 if(!filename.length()) // do silly checks.
889 return false;
891 if(!attachments.size())
892 return false;
894 std::string fn(filename);
895 std::string::size_type p = fn.find_last_of('/');
896 if(p == std::string::npos)
897 p = fn.find_last_of('\\');
898 if(p != std::string::npos) {
899 p +=1; // get past folder delimeter
900 fn = fn.substr(p, fn.length() - p);
902 // fn is now what is stored in the attachments pair as the second parameter
903 // i.e. it->second == fn
904 std::vector<std::pair<std::vector<char>, std::string> >::iterator it;
905 for(it = attachments.begin(); it < attachments.end(); ++it) {
906 if((*it).second == fn) {
907 attachments.erase(it);
908 return true;
911 return false;
914 // returns everything after the '@' synbol in an email address
916 // if there is no '@' symbol returns the empty string.
917 std::string mailer::getserveraddress(const std::string& toaddress) const{
918 if(toaddress.length()) {
919 std::string::size_type pos(toaddress.find("@"));
920 if(pos != std::string::npos) { //found the server beginning
921 if(++pos < toaddress.length())
922 return toaddress.substr(pos, toaddress.length()- pos);
925 return "";
929 // this function has to get an MX record for 'server'
930 // and return its address. Correct form for smtp.
931 // as the domain 'server' may not be the mail server for the server domain!!
932 bool mailer::gethostaddresses(std::vector<SOCKADDR_IN>& adds) {
933 adds.clear(); // be safe in case of my utter stupidity
935 SOCKADDR_IN addr(nameserver, htons(DNS_PORT), AF_INET);
937 hostent* host = 0;
938 if(addr)
939 host = gethostbyaddr(addr.get_sin_addr(), sizeof(addr.ADDR.sin_addr), AF_INET);
940 else
941 host = gethostbyname(nameserver.c_str());
943 if(!host) { // couldn't get to dns, try to connect directly to 'server' instead.
944 ////////////////////////////////////////////////////////////////////////////////
945 // just try to deliver mail directly to "server"
946 // as we didn't get an MX record.
947 // addr.sin_family = AF_INET;
948 addr = SOCKADDR_IN(server, port);
949 addr.ADDR.sin_port = port; // smtp port!! 25
950 if(addr) {
951 host = gethostbyaddr(addr.get_sin_addr(), sizeof(addr.ADDR.sin_addr), AF_INET);
953 else
954 host = gethostbyname(server.c_str());
956 if(!host) {
957 returnstring = "550 Requested action not taken: mailbox unavailable";
958 return false; // error!!!
961 //memcpy((char*)&addr.sin_addr, host->h_addr, host->h_length);
962 std::copy(host->h_addr_list[0], host->h_addr_list[0] + host->h_length, addr.get_sin_addr());
963 adds.push_back(addr);
965 return true;
967 else
968 //memcpy((char*)&addr.sin_addr, host->h_addr, host->h_length);
969 std::copy(host->h_addr_list[0], host->h_addr_list[0] + host->h_length, addr.get_sin_addr());
971 SOCKET s;
972 if(!Socket(s, AF_INET, SOCK_DGRAM, 0)) {
973 returnstring = "451 Requested action aborted: socket function error";
974 return false;
977 if(!Connect(s, addr)) {
978 returnstring = "451 Requested action aborted: dns server unavailable";
979 return false; // dns connection unavailable
982 // dnsheader info id flags num queries
983 unsigned char dns[512] = {1,1, 1,0, 0,1, 0,0, 0,0, 0,0};
984 int dnspos = 12; // end of dns header
985 std::string::size_type stringpos(0);
986 std::string::size_type next(server.find("."));
987 if(next != std::string::npos) { // multipart name e.g. "aserver.somewhere.net"
988 while(stringpos < server.length()) {
989 std::string part(server.substr(stringpos, next-stringpos));
990 dns[dnspos] = part.length();
991 ++dnspos;
992 for(std::string::size_type i = 0; i < part.length(); ++i, ++dnspos) {
993 dns[dnspos] = part[i];
996 stringpos = ++next;
997 next = server.find(".", stringpos);
998 if(next == std::string::npos) {
999 part = server.substr(stringpos, server.length() - stringpos);
1000 dns[dnspos] = part.length();
1001 ++dnspos;
1002 for(std::string::size_type i = 0; i < part.length(); ++i, ++dnspos) {
1003 dns[dnspos] = part[i];
1005 break;
1009 else { // just a single part name. e.g. "aserver"
1010 dns[dnspos] = server.length();
1011 ++dnspos;
1013 for(std::string::size_type i = 0; i < server.length(); ++i, ++dnspos) {
1014 dns[dnspos] = server[i];
1017 // in case the server string has a "." on the end
1018 if(server[server.length()-1] == '.')
1019 dns[dnspos] = 0;
1020 else
1021 dns[dnspos++] = 0;
1023 // add the class & type
1024 dns[dnspos++] = 0;
1025 dns[dnspos++] = 15; // MX record.
1027 dns[dnspos++] = 0;
1028 dns[dnspos++] = 1;
1030 // used to have MSG_DONTROUTE this breaks obviously if you are not
1031 // running a local nameserver and using it (as I used to do so I didn't
1032 // notice until now, oops)
1033 int ret;
1034 if(!Send(ret, s, (char*)dns, dnspos, 0)) {
1035 returnstring = "451 Requested action aborted: server seems to have disconnected.";
1036 Closesocket(s); // clean up
1037 return false;
1039 if(Recv(ret, s, (char*)dns, 512, 0)) {
1040 Closesocket(s);
1041 // now parse the data sent back from the dns for MX records
1042 if(dnspos > 12) { // we got more than a dns header back
1043 unsigned short numsitenames = ((unsigned short)dns[4]<<8) | dns[5];
1044 unsigned short numanswerRR = ((unsigned short)dns[6]<<8) | dns[7];
1045 unsigned short numauthorityRR = ((unsigned short)dns[8]<<8) | dns[9];
1046 unsigned short numadditionalRR = ((unsigned short)dns[10]<<8) | dns[11];
1048 if(!(dns[3] & 0x0F)) { // check for an error
1049 // int auth((dns[2] & 0x04)); // AA bit. the nameserver has given authoritive answer.
1050 int pos = 12; // start after the header.
1052 std::string questionname;
1053 if(numsitenames) {
1054 parsename(pos, dns, questionname);
1055 pos += 4; // move to the next RR
1058 // This gives this warning in VC.
1059 // bloody annoying, there is a way round it according to microsoft.
1060 // The debugger basically cannot browse anything with a name
1061 // longer than 256 characters, "get with the template program MS".
1062 // #pragma warning( disable : 4786 )
1063 // #pragma warning( default : 4786 )
1064 std::vector<std::string> names;
1065 in_addr address;
1066 std::string name;
1067 // VC++ incompatability scoping
1068 // num should be able to be declared in every for loop here
1069 // not in VC
1070 int num = 0;
1071 for(; num < numanswerRR; ++num) {
1072 name = "";
1073 parseRR(pos, dns, name, address);
1074 if(name.length())
1075 names.push_back(name);
1077 for(num = 0; num < numauthorityRR; ++num) {
1078 name = "";
1079 parseRR(pos, dns, name, address);
1080 if(name.length())
1081 names.push_back(name);
1083 for(num = 0; num < numadditionalRR; ++num) {
1084 name = "";
1085 parseRR(pos, dns, name, address);
1086 if(name.length())
1087 names.push_back(name);
1090 // now get all the MX records IP addresess
1091 addr.ADDR.sin_family = AF_INET;
1092 addr.ADDR.sin_port = port; // smtp port!! 25
1093 hostent* host = 0;
1094 for(vec_str_const_iter it = names.begin(); it < names.end(); ++it) {
1095 host = gethostbyname(it->c_str());
1096 if(!host) {
1097 addr.zeroaddress();
1098 continue; // just skip it!!!
1100 std::copy(host->h_addr_list[0], host->h_addr_list[0] + host->h_length, addr.get_sin_addr());
1101 adds.push_back(addr);
1103 // got the addresses
1104 return true;
1108 else
1109 Closesocket(s);
1110 // what are we doing here!!
1111 return false;
1114 // we assume the array 'dns' must be 512 bytes in size!
1115 bool mailer::parseRR(int& pos, const unsigned char dns[], std::string& name, in_addr& address) {
1116 if(pos < 12) // didn,t get more than a header.
1117 return false;
1118 if(pos > 512) // oops.
1119 return false;
1121 int len = dns[pos];
1122 if(len >= 192) { // pointer
1123 int pos1 = dns[++pos];
1124 len = dns[pos1];
1126 else { // not a pointer.
1127 parsename(pos, dns, name);
1129 // If I do not seperate getting the short values to different
1130 // lines of code, the optimizer in VC++ only increments pos once!!!
1131 unsigned short a = ((unsigned short)dns[++pos]<<8);
1132 unsigned short b = dns[++pos];
1133 unsigned short Type = a | b;
1134 a = ((unsigned short)dns[++pos]<<8);
1135 b = dns[++pos];
1136 // unsigned short Class = a | b;
1137 pos += 4; // ttl
1138 a = ((unsigned short)dns[++pos]<<8);
1139 b = dns[++pos];
1140 unsigned short Datalen = a | b;
1141 if(Type == 15) { // MX record
1142 // first two bytes the precedence of the MX server
1143 a = ((unsigned short)dns[++pos]<<8);
1144 b = dns[++pos];
1145 // unsigned short order = a | b; // we don't use this here
1146 len = dns[++pos];
1147 if(len >= 192) {
1148 int pos1 = dns[++pos];
1149 parsename(pos1, dns, name);
1151 else
1152 parsename(pos, dns, name);
1154 else if(Type == 12) { // pointer record
1155 pos += Datalen+1;
1157 else if(Type == 2) { // nameserver
1158 pos += Datalen+1;
1160 else if(Type == 1) { // IP address, Datalen should be 4.
1161 pos += Datalen+1;
1163 else {
1164 pos += Datalen+1;
1166 return true;
1169 void mailer::parsename(int& pos, const unsigned char dns[], std::string& name) {
1170 int len = dns[pos];
1171 if(len >= 192) {
1172 int pos1 = ++pos;
1173 ++pos;
1174 parsename(pos1, dns, name);
1176 else {
1177 for(int i = 0; i < len; ++i)
1178 name += dns[++pos];
1179 len = dns[++pos];
1180 if(len != 0)
1181 name += ".";
1182 if(len >= 192) {
1183 int pos1 = dns[++pos];
1184 ++pos;
1185 parsename(pos1, dns, name);
1187 else if(len > 0) {
1188 parsename(pos, dns, name);
1190 else if(len == 0)
1191 ++pos;
1195 const std::string& mailer::response() const {
1196 return returnstring;
1199 mailer::Address mailer::parseaddress(const std::string& addresstoparse) {
1200 Address newaddress; // return value
1202 // do some silly checks
1203 if(!addresstoparse.length())
1204 return newaddress; // its empty, oops (this should fail at the server.)
1206 if(!addresstoparse.find("@") == std::string::npos) {
1207 // no '@' symbol (could be a local address, e.g. root)
1208 // so just assume this. The SMTP server should just deny delivery if its messed up!
1209 newaddress.address = addresstoparse;
1210 return newaddress;
1212 // we have one angle bracket but not the other
1213 // (this isn't strictly needed, just thought i'd throw it in)
1214 if(((addresstoparse.find('<') != std::string::npos) &&
1215 (addresstoparse.find('>') == std::string::npos)) ||
1216 ((addresstoparse.find('>') != std::string::npos) &&
1217 (addresstoparse.find('<') == std::string::npos))) {
1218 return newaddress; // its empty, oops (this should fail at the server.)
1221 // we have angle bracketed delimitered address
1222 // like this maybe:
1223 // "foo@bar.com"
1224 // or "foo bar <foo@bar.com>"
1225 // or "<foo@bar.com> foo bar"
1226 if((addresstoparse.find('<') != std::string::npos) &&
1227 (addresstoparse.find('>') != std::string::npos)) {
1228 std::string::size_type sta = addresstoparse.find('<');
1229 std::string::size_type end = addresstoparse.find('>');
1231 newaddress.address = addresstoparse.substr(sta + 1, end - sta - 1);
1233 if(sta > 0) { // name at the beginning
1234 // we are cutting off the last character if the bracket address
1235 // continues without a space into the bracketed address
1236 // e.g. "hoopla girl<hoopla@wibble.com>"
1237 // name becomes 'hoopla gir'
1238 // Fix by David Irwin
1239 // old code:
1240 // end = sta -1;
1241 // newaddress.name = addresstoparse.substr(0, end);
1242 newaddress.name = addresstoparse.substr(0, sta);
1243 return newaddress;
1245 else { // name at the end
1246 // no name to get
1247 if(end >= addresstoparse.length()-1)
1248 return newaddress;
1250 end += 2;
1251 if(end >= addresstoparse.length())
1252 return newaddress;
1254 newaddress.name = addresstoparse.substr(end, addresstoparse.length()- end);
1255 // remove whitespace from end if need be
1256 if(newaddress.name[newaddress.name.length()-1] == ' ')
1257 newaddress.name = newaddress.name.substr(0, newaddress.name.length()-1);
1258 return newaddress;
1261 // if we get here assume an address of the form: foo@bar.com
1262 // and just save it.
1263 newaddress.address = addresstoparse;
1265 return newaddress;
1268 // set the authentication type
1269 void mailer::authtype(const enum authtype Type) {
1270 assert(Type == LOGIN || Type == PLAIN);
1271 type = Type;
1274 // set the username for authentication.
1275 // If this function is called with a non empty string
1276 // jwSMTP will try to use authentication.
1277 // To not use authentication after this, call again
1278 // with the empty string e.g.
1279 // mailerobject.username("");
1280 void mailer::username(const std::string& User) {
1281 auth = (User.length() != 0);
1282 user = User;
1285 // set the password for authentication
1286 void mailer::password(const std::string& Pass) {
1287 pass = Pass;
1290 // authenticate against a server.
1291 bool mailer::authenticate(const std::string& servergreeting, const SOCKET& s) {
1292 assert(auth && user.length()); // shouldn't be calling this function if this is not set!
1293 int len(0);
1294 if(!user.length()) { // obvioulsy a big whoops
1295 Send(len, s, "QUIT\r\n", 6, 0);
1296 return false;
1299 // now parse the servergreeting looking for the auth type 'type'
1300 // if 'type' is not present exit with error (return false)
1301 std::string at;
1302 if(type == LOGIN)
1303 at = "LOGIN";
1304 else if(type == PLAIN)
1305 at = "PLAIN";
1306 else { // oopsy no other auth types yet!! MUST BE A BUG
1307 assert(false);
1308 returnstring = "554 jwSMTP only handles LOGIN or PLAIN authentication at present!";
1309 Send(len, s, "QUIT\r\n", 6, 0);
1310 return false;
1313 // uppercase servergreeting first.
1314 std::string greeting(servergreeting);
1315 //char ch;
1316 for(std::string::size_type pos = 0; pos < greeting.length(); ++pos) {
1317 //ch = greeting[pos];
1318 greeting[pos] = toupper(greeting[pos] /*ch*/);
1320 if(greeting.find(at) == std::string::npos) {
1321 returnstring = "554 jwSMTP only handles LOGIN or PLAIN authentication at present!";
1322 Send(len, s, "QUIT\r\n", 6, 0);
1323 return false; // didn't find that type of login!
1326 // ok try and authenticate to the server.
1327 const int buffsize(1024);
1328 char buff[buffsize];
1329 if(type == LOGIN) {
1330 greeting = "auth " + at + "\r\n";
1331 if(!Send(len, s, greeting.c_str(), greeting.length(), 0)) {
1332 returnstring = "554 send failure: \"auth " + at + "\"";
1333 return false;
1335 if(!Recv(len, s, buff, buffsize, 0)) {
1336 returnstring = "554 receive failure: waiting on username question!";
1337 return false;
1339 buff[len] = '\0';
1340 returnstring = buff;
1342 // The server should give us a "334 VXNlcm5hbWU6" base64 username
1343 if(returnstring.substr(0,16) != "334 VXNlcm5hbWU6") {
1344 // returnstring = "554 Server did not return correct response to \'auth login\' command";
1345 Send(len, s, "QUIT\r\n", 6, 0);
1346 return false;
1348 greeting = base64encode(user, false) + "\r\n";
1349 if(!Send(len, s, greeting.c_str(), greeting.length(), 0)) {
1350 returnstring = "554 send failure: sending username";
1351 return false;
1353 // now get the password question
1354 if(!Recv(len, s, buff, buffsize, 0)) {
1355 returnstring = "554 receive failure: waiting on password question!";
1356 return false;
1358 buff[len] = '\0';
1359 returnstring = buff;
1360 // The server should give us a "334 UGFzc3dvcmQ6" base64 password
1361 if(returnstring.substr(0,16) != "334 UGFzc3dvcmQ6") {
1362 // returnstring = "554 Server did not return correct password question";
1363 Send(len, s, "QUIT\r\n", 6, 0);
1364 return false;
1366 greeting = base64encode(pass, false) + "\r\n";
1367 if(!Send(len, s, greeting.c_str(), greeting.length(), 0)) {
1368 returnstring = "554 send failure: sending password";
1369 return false;
1371 // now see if we are authenticated.
1372 if(!Recv(len, s, buff, buffsize, 0)) {
1373 returnstring = "554 receive failure: waiting on auth login response!";
1374 return false;
1376 buff[len] = '\0';
1377 returnstring = buff;
1378 if(returnstring.substr(0,3) == "235")
1379 return true;
1381 // PLAIN authetication
1382 else if(type == PLAIN) { // else if not needed, being anal
1383 // now create the authentication response and send it.
1384 // username\0username\0password\r\n
1385 // i.e. \0fred\0secret\r\n (blank identity)
1386 std::vector<char> enc;
1387 std::string::size_type pos = 0;
1388 for(; pos < user.length(); ++pos)
1389 enc.push_back(user[pos]);
1390 enc.push_back('\0');
1391 for(pos = 0; pos < user.length(); ++pos)
1392 enc.push_back(user[pos]);
1393 enc.push_back('\0');
1394 for(pos = 0; pos < pass.length(); ++pos)
1395 enc.push_back(pass[pos]);
1397 enc = base64encode(enc, false);
1398 greeting = "auth plain ";
1399 for(std::vector<char>::const_iterator it1 = enc.begin(); it1 < enc.end(); ++it1)
1400 greeting += *it1;
1401 greeting += "\r\n";
1403 if(!Send(len, s, greeting.c_str(), greeting.length(), 0)) {
1404 returnstring = "554 send failure: sending login:plain authenication info";
1405 return false;
1407 if(!Recv(len, s, buff, buffsize, 0)) {
1408 returnstring = "554 receive failure: waiting on auth plain autheticated response!";
1409 return false;
1411 buff[len] = '\0';
1412 returnstring = buff;
1413 if(returnstring.substr(0,3) == "235")
1414 return true;
1417 // fall through return an error.
1418 Send(len, s, "QUIT\r\n", 6, 0);
1419 return false;
1422 } // end namespace jwsmtp