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.
5 // This file is part of the jwSMTP library.
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
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*.
31 #include <sstream> // ostrstream
32 #include <ctime> // for localtime
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
),
45 server(getserveraddress(TOaddress
)),
46 nameserver(Nameserver
),
47 port(htons(Port
)), // make the 'port' network byte order.
48 lookupMXRecord(MXLookup
),
50 // Parse the email addresses into an Address structure.
51 setsender(FROMaddress
);
52 addrecipient(TOaddress
);
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
),
62 server(getserveraddress(TOaddress
)),
63 nameserver(Nameserver
),
64 port(htons(Port
)), // make the 'port' network byte order.
65 lookupMXRecord(MXLookup
),
67 // Parse the email addresses into an Address structure.
68 setsender(FROMaddress
);
69 addrecipient(TOaddress
);
74 mailer::mailer(bool MXLookup
, unsigned short Port
):
77 lookupMXRecord(MXLookup
),
83 bool mailer::setmessage(const std::string
& newmessage
) {
84 if(!newmessage
.length())
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
]);
96 bool mailer::setmessage(const std::vector
<char>& newmessage
) {
97 if(!newmessage
.size())
100 message
= newmessage
;
107 bool mailer::setmessageHTML(const std::string
& newmessage
) {
108 if(!newmessage
.length())
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
);
119 bool mailer::setmessageHTML(const std::vector
<char>& newmessage
) {
120 if(!newmessage
.size())
123 messageHTML
= base64encode(newmessage
);
128 bool mailer::setmessageHTMLfile(const std::string
& filename
) {
129 if(!filename
.length())
132 std::ifstream
file(filename
.c_str(), std::ios::binary
| std::ios::in
);
135 std::vector
<char> filedata
;
137 for(; file
.good(); c
= file
.get()) {
140 filedata
.push_back(c
);
143 messageHTML
= base64encode(filedata
);
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.
159 if(it
== message
.begin()) {
160 it
= message
.insert(it
, '\r');
161 ++it
; // step past newline
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
, '.');
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 '.'
193 if( ((it
+ 1) != message
.end()) && (*(it
+1) == '.') ) {
194 it
= message
.insert(it
+ 1, '.');
201 // don't do anything if we are not longer than a 1000 characters
202 if(message
.size() < 1000)
205 // now we have checked line breaks
206 // check line lengths.
208 for(it
= message
.begin(); it
< message
.end(); ++it
, ++count
) {
210 count
= 0; // reset for a new line.
211 ++it
; // get past newline
214 else if(count
>= 998) {
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
) {
223 it
= ++pos
; // get past the space.
228 if(it
< message
.end())
229 it
= message
.insert(it
, '\r');
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
) {
240 count
= 0; // reset for a new line.
241 ++it
; // get past newline
244 else if(count
>= 998) {
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
) {
253 it
= ++pos
; // get past the space.
258 if(it
< messageHTML
.end())
259 it
= messageHTML
.insert(it
, '\r');
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())
273 subject
= newSubject
;
277 bool mailer::setserver(const std::string
& nameserver_or_smtpserver
) {
278 if(!nameserver_or_smtpserver
.length())
281 nameserver
= nameserver_or_smtpserver
;
285 bool mailer::setsender(const std::string
& newsender
) {
286 if(!newsender
.length())
289 Address
newaddress(parseaddress(newsender
));
291 fromAddress
= newaddress
;
295 bool mailer::addrecipient(const std::string
& newrecipient
, short recipient_type
) {
296 // SMTP only allows 100 recipients max at a time.
298 if(recipients
.size() >= 100) // == would be fine, but let's be stupid safe
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
));
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
);
328 // fall through as we did not find this recipient
333 void mailer::clearrecipients() {
337 void mailer::clearattachments() {
341 void mailer::reset() {
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.
352 returnstring
= ""; // clear out any errors from previous use
355 // convenience function
356 void mailer::send() {
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";
368 if(!fromAddress
.address
.length()) {
369 returnstring
= "451 Requested action aborted: local error who am I";
372 if(!nameserver
.length()) {
373 returnstring
= "451 Requested action aborted: local error no SMTP/name server/smtp server";
377 std::vector
<SOCKADDR_IN
> adds
;
379 if(!gethostaddresses(adds
)) {
380 // error!! we are dead.
381 returnstring
= "451 Requested action aborted: No MX records ascertained";
385 else { // connect directly to an SMTP server.
386 SOCKADDR_IN
addr(nameserver
, port
, AF_INET
);
389 host
= gethostbyaddr(addr
.get_sin_addr(), sizeof(addr
.ADDR
.sin_addr
), AF_INET
);
392 host
= gethostbyname(nameserver
.c_str());
394 returnstring
= "451 Requested action aborted: local error in processing";
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
);
403 if(!Socket(s
, AF_INET
, SOCK_STREAM
, 0)) {
404 returnstring
= "451 Requested action aborted: socket function error";
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.";
424 // 220 the server line returned here
426 if(!Recv(len1
, s
, buff
, buffsize
-1, 0)) {
427 returnstring
= "554 Transaction failed: server connect response error.";
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";
444 if(!Recv(len1
, s
, buff
, buffsize
-1, 0)) {
445 returnstring
= "554 Transaction failed: EHLO receipt";
450 std::string greeting
= returnstring
= buff
;
451 if(returnstring
.substr(0,3) != OK
) {
453 // oops no ESMTP but using authentication no go bail out!
454 returnstring
= "554 possibly trying to use AUTH without ESMTP server, ERROR!";
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";
465 if(!Recv(len1
, s
, buff
, buffsize
-1, 0)) {
466 returnstring
= "554 Transaction failed: HELO receipt";
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);
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.
487 if(!authenticate(greeting
, s
))
488 continue; // try the next server, you never know!!
491 // S: MAIL FROM:<Smith@Alpha.ARPA>
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";
501 if(!Recv(len1
, s
, buff
, buffsize
-1, 0)) {
502 returnstring
= "554 MAIL FROM receipt error";
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);
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.
521 for(recipient_const_iter recip
= recipients
.begin(); recip
< recipients
.end(); ++recip
) {
524 // S: RCPT TO:<Jones@Beta.ARPA>
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>
532 if(!Send(len1
, s
, commandline
.c_str(), commandline
.length(), 0)) {
533 returnstring
= "554 Transaction failed";
536 if(!Recv(len1
, s
, buff
, buffsize
-1, 0)) {
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();
558 // R: 354 Start mail input; end with <CRLF>.<CRLF>
559 // S: Blah blah blah...
560 // S: ...etc. etc. etc.
564 if(!Send(len1
, s
, "DATA\r\n", 6, 0)) {
565 returnstring
= "554 DATA send error";
568 if(!Recv(len1
, s
, buff
, buffsize
-1, 0)) {
569 returnstring
= "554 DATA, server response error";
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);
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)";
591 if(!Recv(len1
, s
, buff
, buffsize
-1, 0)) {
592 returnstring
= "554 DATA, server response error (actual send)";
596 // The server should give us a 250 reply if the mail was delivered okay
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);
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.
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";
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());
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();
673 headerline
+= "To: ";
674 for(it
= to
.begin(); it
< to
.end(); ++it
) {
676 if(count
> 1 && ((it
+ 1) < to
.end()) )
677 headerline
+= ",\r\n "; // must add a space after the comma
679 headerline
+= "\r\n";
683 headerline
+= "Cc: ";
684 for(it
= cc
.begin(); it
< cc
.end(); ++it
) {
686 if(count
> 1 && ((it
+ 1) < cc
.end()) )
687 headerline
+= ",\r\n "; // must add a space after the comma
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();
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
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");
709 if(attachments
.size() || messageHTML
.size())
712 if(MIME
) { // we have attachments
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());
721 ///////////////////////////////////////////////////////////////////////////
722 // add the current time.
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)
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 ///////////////////////////////////////////////////////////////////////////
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 ///////////////////////////////////////////////////////////////////////////
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";
786 headerline
+= "\r\n--" + boundary
+ "\r\n";
787 ///////////////////////////////////
789 ret
.insert(ret
.end(), headerline
.begin(), headerline
.end());
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
820 headerline
+= "Content-Type: application/X-other-1;\r\n";
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());
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";
840 headerline
+= "\r\n\r\n--" + boundary
+ "\r\n";
841 ret
.insert(ret
.end(), headerline
.begin(), headerline
.end());
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());
855 bool mailer::attach(const std::string
& filename
) {
856 if(!filename
.length()) // do silly checks.
859 std::ifstream
file(filename
.c_str(), std::ios::binary
| std::ios::in
);
863 std::vector
<char> filedata
;
865 for(; file
.good(); c
= file
.get()) {
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
));
887 bool mailer::removeattachment(const std::string
& filename
) {
888 if(!filename
.length()) // do silly checks.
891 if(!attachments
.size())
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
);
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
);
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
);
939 host
= gethostbyaddr(addr
.get_sin_addr(), sizeof(addr
.ADDR
.sin_addr
), AF_INET
);
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
951 host
= gethostbyaddr(addr
.get_sin_addr(), sizeof(addr
.ADDR
.sin_addr
), AF_INET
);
954 host
= gethostbyname(server
.c_str());
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
);
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());
972 if(!Socket(s
, AF_INET
, SOCK_DGRAM
, 0)) {
973 returnstring
= "451 Requested action aborted: socket function error";
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();
992 for(std::string::size_type i
= 0; i
< part
.length(); ++i
, ++dnspos
) {
993 dns
[dnspos
] = part
[i
];
997 next
= server
.find(".", stringpos
);
998 if(next
== std::string::npos
) {
999 part
= server
.substr(stringpos
, server
.length() - stringpos
);
1000 dns
[dnspos
] = part
.length();
1002 for(std::string::size_type i
= 0; i
< part
.length(); ++i
, ++dnspos
) {
1003 dns
[dnspos
] = part
[i
];
1009 else { // just a single part name. e.g. "aserver"
1010 dns
[dnspos
] = server
.length();
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] == '.')
1023 // add the class & type
1025 dns
[dnspos
++] = 15; // MX record.
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)
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
1039 if(Recv(ret
, s
, (char*)dns
, 512, 0)) {
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
;
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
;
1067 // VC++ incompatability scoping
1068 // num should be able to be declared in every for loop here
1071 for(; num
< numanswerRR
; ++num
) {
1073 parseRR(pos
, dns
, name
, address
);
1075 names
.push_back(name
);
1077 for(num
= 0; num
< numauthorityRR
; ++num
) {
1079 parseRR(pos
, dns
, name
, address
);
1081 names
.push_back(name
);
1083 for(num
= 0; num
< numadditionalRR
; ++num
) {
1085 parseRR(pos
, dns
, name
, address
);
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
1094 for(vec_str_const_iter it
= names
.begin(); it
< names
.end(); ++it
) {
1095 host
= gethostbyname(it
->c_str());
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
1110 // what are we doing here!!
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.
1118 if(pos
> 512) // oops.
1122 if(len
>= 192) { // pointer
1123 int pos1
= dns
[++pos
];
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);
1136 // unsigned short Class = a | b;
1138 a
= ((unsigned short)dns
[++pos
]<<8);
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);
1145 // unsigned short order = a | b; // we don't use this here
1148 int pos1
= dns
[++pos
];
1149 parsename(pos1
, dns
, name
);
1152 parsename(pos
, dns
, name
);
1154 else if(Type
== 12) { // pointer record
1157 else if(Type
== 2) { // nameserver
1160 else if(Type
== 1) { // IP address, Datalen should be 4.
1169 void mailer::parsename(int& pos
, const unsigned char dns
[], std::string
& name
) {
1174 parsename(pos1
, dns
, name
);
1177 for(int i
= 0; i
< len
; ++i
)
1183 int pos1
= dns
[++pos
];
1185 parsename(pos1
, dns
, name
);
1188 parsename(pos
, dns
, name
);
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
;
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
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
1241 // newaddress.name = addresstoparse.substr(0, end);
1242 newaddress
.name
= addresstoparse
.substr(0, sta
);
1245 else { // name at the end
1247 if(end
>= addresstoparse
.length()-1)
1251 if(end
>= addresstoparse
.length())
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);
1261 // if we get here assume an address of the form: foo@bar.com
1262 // and just save it.
1263 newaddress
.address
= addresstoparse
;
1268 // set the authentication type
1269 void mailer::authtype(const enum authtype Type
) {
1270 assert(Type
== LOGIN
|| Type
== PLAIN
);
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);
1285 // set the password for authentication
1286 void mailer::password(const std::string
& 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!
1294 if(!user
.length()) { // obvioulsy a big whoops
1295 Send(len
, s
, "QUIT\r\n", 6, 0);
1299 // now parse the servergreeting looking for the auth type 'type'
1300 // if 'type' is not present exit with error (return false)
1304 else if(type
== PLAIN
)
1306 else { // oopsy no other auth types yet!! MUST BE A BUG
1308 returnstring
= "554 jwSMTP only handles LOGIN or PLAIN authentication at present!";
1309 Send(len
, s
, "QUIT\r\n", 6, 0);
1313 // uppercase servergreeting first.
1314 std::string
greeting(servergreeting
);
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
];
1330 greeting
= "auth " + at
+ "\r\n";
1331 if(!Send(len
, s
, greeting
.c_str(), greeting
.length(), 0)) {
1332 returnstring
= "554 send failure: \"auth " + at
+ "\"";
1335 if(!Recv(len
, s
, buff
, buffsize
, 0)) {
1336 returnstring
= "554 receive failure: waiting on username question!";
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);
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";
1353 // now get the password question
1354 if(!Recv(len
, s
, buff
, buffsize
, 0)) {
1355 returnstring
= "554 receive failure: waiting on password question!";
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);
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";
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!";
1377 returnstring
= buff
;
1378 if(returnstring
.substr(0,3) == "235")
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
)
1403 if(!Send(len
, s
, greeting
.c_str(), greeting
.length(), 0)) {
1404 returnstring
= "554 send failure: sending login:plain authenication info";
1407 if(!Recv(len
, s
, buff
, buffsize
, 0)) {
1408 returnstring
= "554 receive failure: waiting on auth plain autheticated response!";
1412 returnstring
= buff
;
1413 if(returnstring
.substr(0,3) == "235")
1417 // fall through return an error.
1418 Send(len
, s
, "QUIT\r\n", 6, 0);
1422 } // end namespace jwsmtp