git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@12671 a95241bf-73f2-0310-859d-f6bbb57e9c96
1061 lines
26 KiB
C++
1061 lines
26 KiB
C++
/* SMTPProtocol - implementation of the SMTP protocol
|
|
**
|
|
** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved.
|
|
*/
|
|
|
|
|
|
#include <DataIO.h>
|
|
#include <Message.h>
|
|
#include <Alert.h>
|
|
#include <TextControl.h>
|
|
#include <Entry.h>
|
|
#include <Path.h>
|
|
|
|
#include <cctype>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <netdb.h>
|
|
|
|
#include <status.h>
|
|
#include <ProtocolConfigView.h>
|
|
#include <mail_encoding.h>
|
|
#include <MailSettings.h>
|
|
#include <ChainRunner.h>
|
|
#include <crypt.h>
|
|
#include <unistd.h>
|
|
|
|
#include <map>
|
|
|
|
#include "smtp.h"
|
|
#ifndef USESSL
|
|
#include "md5.h"
|
|
#endif
|
|
|
|
#include <MDRLanguage.h>
|
|
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
#include <sys/select.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#define CRLF "\r\n"
|
|
#define SMTP_RESPONSE_SIZE 8192
|
|
|
|
#ifdef DEBUG
|
|
# define D(x) x
|
|
# define bug printf
|
|
#else
|
|
# define D(x) ;
|
|
#endif
|
|
|
|
/*
|
|
** Function: md5_hmac
|
|
** taken from the file rfc2104.txt
|
|
** written by Martin Schaaf <[email protected]>
|
|
*/
|
|
void
|
|
MD5Hmac(unsigned char *digest,
|
|
const unsigned char* text, int text_len,
|
|
const unsigned char* key, int key_len)
|
|
{
|
|
MD5_CTX context;
|
|
unsigned char k_ipad[64]; /* inner padding -
|
|
* key XORd with ipad
|
|
*/
|
|
unsigned char k_opad[64]; /* outer padding -
|
|
* key XORd with opad
|
|
*/
|
|
/* unsigned char tk[16]; */
|
|
int i;
|
|
|
|
/* start out by storing key in pads */
|
|
memset(k_ipad, 0, sizeof k_ipad);
|
|
memset(k_opad, 0, sizeof k_opad);
|
|
if (key_len > 64) {
|
|
/* if key is longer than 64 bytes reset it to key=MD5(key) */
|
|
MD5_CTX tctx;
|
|
|
|
MD5_Init(&tctx);
|
|
MD5_Update(&tctx, (unsigned char*)key, key_len);
|
|
MD5_Final(k_ipad, &tctx);
|
|
MD5_Final(k_opad, &tctx);
|
|
} else {
|
|
memcpy(k_ipad, key, key_len);
|
|
memcpy(k_opad, key, key_len);
|
|
}
|
|
|
|
/*
|
|
* the HMAC_MD5 transform looks like:
|
|
*
|
|
* MD5(K XOR opad, MD5(K XOR ipad, text))
|
|
*
|
|
* where K is an n byte key
|
|
* ipad is the byte 0x36 repeated 64 times
|
|
* opad is the byte 0x5c repeated 64 times
|
|
* and text is the data being protected
|
|
*/
|
|
|
|
|
|
/* XOR key with ipad and opad values */
|
|
for (i = 0; i < 64; i++) {
|
|
k_ipad[i] ^= 0x36;
|
|
k_opad[i] ^= 0x5c;
|
|
}
|
|
|
|
/*
|
|
* perform inner MD5
|
|
*/
|
|
MD5_Init(&context); /* init context for 1st
|
|
* pass */
|
|
MD5_Update(&context, k_ipad, 64); /* start with inner pad */
|
|
MD5_Update(&context, (unsigned char*)text, text_len); /* then text of datagram */
|
|
MD5_Final(digest, &context); /* finish up 1st pass */
|
|
/*
|
|
* perform outer MD5
|
|
*/
|
|
MD5_Init(&context); /* init context for 2nd
|
|
* pass */
|
|
MD5_Update(&context, k_opad, 64); /* start with outer pad */
|
|
MD5_Update(&context, digest, 16); /* then results of 1st
|
|
* hash */
|
|
MD5_Final(digest, &context); /* finish up 2nd pass */
|
|
}
|
|
|
|
|
|
void
|
|
MD5HexHmac(char *hexdigest,
|
|
const unsigned char* text, int text_len,
|
|
const unsigned char* key, int key_len)
|
|
{
|
|
unsigned char digest[16];
|
|
int i;
|
|
unsigned char c;
|
|
|
|
MD5Hmac(digest, text, text_len, key, key_len);
|
|
for (i = 0; i < 16; i++) {
|
|
c = digest[i];
|
|
*hexdigest++ = (c > 0x9F ? 'a'-10 : '0')+(c>>4);
|
|
*hexdigest++ = ((c&0x0F) > 9 ? 'a'-10 : '0')+(c&0x0F);
|
|
}
|
|
*hexdigest = '\0';
|
|
}
|
|
|
|
|
|
/*
|
|
** Function: MD5Sum
|
|
** generates an MD5-sum from the given string
|
|
*/
|
|
void
|
|
MD5Sum (char* sum, unsigned char *text, int text_len) {
|
|
MD5_CTX context;
|
|
MD5_Init(&context);
|
|
MD5_Update(&context, text, text_len);
|
|
MD5_Final((unsigned char*)sum, &context);
|
|
sum[16] = '\0';
|
|
}
|
|
|
|
/*
|
|
** Function: MD5Digest
|
|
** generates an MD5-digest from the given string
|
|
*/
|
|
void MD5Digest (char* hexdigest, unsigned char *text, int text_len) {
|
|
int i;
|
|
unsigned char digest[17];
|
|
unsigned char c;
|
|
|
|
MD5Sum((char*)digest, text, text_len);
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
c = digest[i];
|
|
*hexdigest++ = (c > 0x9F ? 'a'-10 : '0')+(c>>4);
|
|
*hexdigest++ = ((c&0x0F) > 9 ? 'a'-10 : '0')+(c&0x0F);
|
|
}
|
|
*hexdigest = '\0';
|
|
}
|
|
|
|
/*
|
|
** Function: SplitChallengeIntoMap
|
|
** splits a challenge-string into the given map (see RFC-2831)
|
|
*/
|
|
// :
|
|
static bool
|
|
SplitChallengeIntoMap(BString str, map<BString,BString>& m)
|
|
{
|
|
m.clear();
|
|
const char* key;
|
|
const char* val;
|
|
char* s = (char*)str.String();
|
|
while(*s != 0) {
|
|
while(isspace(*s))
|
|
s++;
|
|
key = s;
|
|
while(isalpha(*s))
|
|
s++;
|
|
if (*s != '=')
|
|
return false;
|
|
*s++ = '\0';
|
|
while(isspace(*s))
|
|
s++;
|
|
if (*s=='"') {
|
|
val = ++s;
|
|
while(*s!='"') {
|
|
if (*s == 0)
|
|
return false;
|
|
s++;
|
|
}
|
|
*s++ = '\0';
|
|
} else {
|
|
val = s;
|
|
while(*s!=0 && *s!=',' && !isspace(*s))
|
|
s++;
|
|
if (*s != 0)
|
|
*s++ = '\0';
|
|
}
|
|
m[key] = val;
|
|
while(isspace(*s))
|
|
s++;
|
|
if (*s != ',')
|
|
return false;
|
|
s++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// Authentication types recognized. Not all methods are implemented.
|
|
enum AuthType {
|
|
LOGIN = 1,
|
|
PLAIN = 1 << 2,
|
|
CRAM_MD5 = 1 << 3,
|
|
DIGEST_MD5 = 1 << 4
|
|
};
|
|
|
|
|
|
SMTPProtocol::SMTPProtocol(BMessage *message, BMailChainRunner *run)
|
|
: BMailFilter(message),
|
|
fSettings(message),
|
|
runner(run),
|
|
fAuthType(0)
|
|
{
|
|
BString error_msg;
|
|
int32 authMethod = fSettings->FindInt32("auth_method");
|
|
|
|
if (authMethod == 2) {
|
|
// POP3 authentification is handled here instead of SMTPProtocol::Login()
|
|
// because some servers obviously don't like establishing the connection
|
|
// to the SMTP server first...
|
|
fStatus = POP3Authentification();
|
|
if (fStatus < B_OK) {
|
|
error_msg << MDR_DIALECT_CHOICE ("POP3 authentification failed. The server said:\n","POP3認証に失敗しました\n") << fLog;
|
|
runner->ShowError(error_msg.String());
|
|
runner->Stop(true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
fStatus = Open(fSettings->FindString("server"), fSettings->FindInt32("port"), authMethod == 1);
|
|
if (fStatus < B_OK) {
|
|
error_msg << MDR_DIALECT_CHOICE ("Error while opening connection to ","接続中にエラーが発生しました") << fSettings->FindString("server");
|
|
|
|
if (fSettings->FindInt32("port") > 0)
|
|
error_msg << ":" << fSettings->FindInt32("port");
|
|
|
|
// << strerror(err) - BNetEndpoint sucks, we can't use this;
|
|
if (fLog.Length() > 0)
|
|
error_msg << ". The server says:\n" << fLog;
|
|
else
|
|
error_msg << MDR_DIALECT_CHOICE (": Connection refused or host not found.",";接続が拒否されたかサーバーが見つかりません");
|
|
|
|
runner->ShowError(error_msg.String());
|
|
runner->Stop(true);
|
|
return;
|
|
}
|
|
|
|
const char *password = fSettings->FindString("password");
|
|
char *passwd = get_passwd(fSettings, "cpasswd");
|
|
if (passwd)
|
|
password = passwd;
|
|
|
|
fStatus = Login(fSettings->FindString("username"), password);
|
|
delete passwd;
|
|
|
|
if (fStatus < B_OK) {
|
|
//-----This is a really cool kind of error message. How can we make it work for POP3?
|
|
error_msg << MDR_DIALECT_CHOICE ("Error while logging in to ","ログイン中にエラーが発生しました\n") << fSettings->FindString("server")
|
|
<< MDR_DIALECT_CHOICE (". The server said:\n","サーバーエラー\n") << fLog;
|
|
runner->ShowError(error_msg.String());
|
|
runner->Stop(true);
|
|
}
|
|
}
|
|
|
|
|
|
SMTPProtocol::~SMTPProtocol()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
|
|
// Check for errors?
|
|
status_t
|
|
SMTPProtocol::InitCheck(BString *verbose)
|
|
{
|
|
if (verbose != NULL && fStatus < B_OK) {
|
|
*verbose << MDR_DIALECT_CHOICE ("Error while fetching mail from ","受信中にエラーが発生しました") << fSettings->FindString("server")
|
|
<< ": " << strerror(fStatus);
|
|
}
|
|
return fStatus;
|
|
}
|
|
|
|
|
|
// Process EMail to be sent
|
|
|
|
status_t
|
|
SMTPProtocol::ProcessMailMessage(BPositionIO **io_message, BEntry */*io_entry*/,
|
|
BMessage *io_headers, BPath */*io_folder*/, const char */*io_uid*/)
|
|
{
|
|
const char *from = io_headers->FindString("MAIL:from");
|
|
const char *to = io_headers->FindString("MAIL:recipients");
|
|
if (!to)
|
|
to = io_headers->FindString("MAIL:to");
|
|
|
|
if (to == NULL || from == NULL)
|
|
fLog = "Invalid message headers";
|
|
|
|
if (to && from && Send(to, from, *io_message) == B_OK) {
|
|
runner->ReportProgress(0, 1);
|
|
return B_OK;
|
|
}
|
|
|
|
BString error;
|
|
MDR_DIALECT_CHOICE (
|
|
error << "An error occurred while sending the message " << io_headers->FindString("MAIL:subject") << " to " << to << ":\n" << fLog;,
|
|
error << io_headers->FindString("MAIL:subject") << "を" << to << "\nへ送信中にエラーが発生しました:\n" << fLog;
|
|
)
|
|
|
|
runner->ShowError(error.String());
|
|
runner->ReportProgress(0, 1);
|
|
return B_ERROR;
|
|
}
|
|
|
|
|
|
// Opens connection to server
|
|
|
|
status_t
|
|
SMTPProtocol::Open(const char *address, int port, bool esmtp)
|
|
{
|
|
runner->ReportProgress(0, 0, MDR_DIALECT_CHOICE ("Connecting to server...","接続中..."));
|
|
|
|
#ifdef USESSL
|
|
use_ssl = (fSettings->FindInt32("flavor") == 1);
|
|
ssl = NULL;
|
|
ctx = NULL;
|
|
#endif
|
|
|
|
if (port <= 0)
|
|
#ifdef USESSL
|
|
port = use_ssl ? 465 : 25;
|
|
#else
|
|
port = 25;
|
|
#endif
|
|
|
|
uint32 hostIP = inet_addr(address); // first see if we can parse it as a numeric address
|
|
if ((hostIP == 0)||(hostIP == (uint32)-1)) {
|
|
struct hostent * he = gethostbyname(address);
|
|
hostIP = he ? *((uint32*)he->h_addr) : 0;
|
|
}
|
|
|
|
if (hostIP == 0)
|
|
return EHOSTUNREACH;
|
|
|
|
#ifdef BONE
|
|
_fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
#else
|
|
_fd = socket(AF_INET, 2, 0);
|
|
#endif
|
|
if (_fd >= 0) {
|
|
struct sockaddr_in saAddr;
|
|
memset(&saAddr, 0, sizeof(saAddr));
|
|
saAddr.sin_family = AF_INET;
|
|
saAddr.sin_port = htons(port);
|
|
saAddr.sin_addr.s_addr = hostIP;
|
|
int result = connect(_fd, (struct sockaddr *) &saAddr, sizeof(saAddr));
|
|
if (result < 0) {
|
|
#ifdef BONE
|
|
close(_fd);
|
|
#else
|
|
closesocket(_fd);
|
|
#endif
|
|
_fd = -1;
|
|
return errno;
|
|
}
|
|
} else {
|
|
return errno;
|
|
}
|
|
|
|
#ifdef USESSL
|
|
if (use_ssl) {
|
|
SSL_library_init();
|
|
SSL_load_error_strings();
|
|
RAND_seed(this,sizeof(SMTPProtocol));
|
|
/*--- Because we're an add-on loaded at an unpredictable time, all
|
|
the memory addresses and things contained in ourself are
|
|
esssentially random. */
|
|
|
|
ctx = SSL_CTX_new(SSLv23_method());
|
|
ssl = SSL_new(ctx);
|
|
sbio=BIO_new_socket(_fd,BIO_NOCLOSE);
|
|
SSL_set_bio(ssl,sbio,sbio);
|
|
|
|
if (SSL_connect(ssl) <= 0) {
|
|
BString error;
|
|
error << "Could not connect to SMTP server " << fSettings->FindString("server");
|
|
if (port != 465)
|
|
error << ":" << port;
|
|
error << ". (SSL Connection Error)";
|
|
runner->ShowError(error.String());
|
|
SSL_CTX_free(ctx);
|
|
#ifdef BONE
|
|
close(_fd);
|
|
#else
|
|
closesocket(_fd);
|
|
#endif
|
|
_fd = -1;
|
|
runner->Stop(true);
|
|
return B_OK;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
BString line;
|
|
ReceiveResponse(line);
|
|
|
|
char localhost[255];
|
|
gethostname(localhost,255);
|
|
|
|
if (localhost[0] == 0)
|
|
strcpy(localhost,"namethisbebox");
|
|
|
|
char *cmd = new char[::strlen(localhost)+8];
|
|
if (!esmtp)
|
|
::sprintf(cmd,"HELO %s"CRLF, localhost);
|
|
else
|
|
::sprintf(cmd,"EHLO %s"CRLF, localhost);
|
|
|
|
if (SendCommand(cmd) != B_OK) {
|
|
delete[] cmd;
|
|
return B_ERROR;
|
|
}
|
|
|
|
delete[] cmd;
|
|
|
|
// Check auth type
|
|
if (esmtp) {
|
|
const char *res = fLog.String();
|
|
char *p;
|
|
if ((p = ::strstr(res, "250-AUTH")) != NULL) {
|
|
if(::strstr(p, "LOGIN"))
|
|
fAuthType |= LOGIN;
|
|
if(::strstr(p, "PLAIN"))
|
|
fAuthType |= PLAIN;
|
|
if(::strstr(p, "CRAM-MD5"))
|
|
fAuthType |= CRAM_MD5;
|
|
if(::strstr(p, "DIGEST-MD5")) {
|
|
fAuthType |= DIGEST_MD5;
|
|
fServerName = address;
|
|
}
|
|
}
|
|
}
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
SMTPProtocol::POP3Authentification()
|
|
{
|
|
// find the POP3 filter of the other chain - identify by name...
|
|
BList chains;
|
|
if (GetInboundMailChains(&chains) < B_OK) {
|
|
fLog = "Cannot get inbound chains";
|
|
return B_ERROR;
|
|
}
|
|
|
|
BMailChainRunner *parent = runner;
|
|
BMailChain *chain = NULL;
|
|
for (int i = chains.CountItems(); i-- > 0;)
|
|
{
|
|
chain = (BMailChain *)chains.ItemAt(i);
|
|
if (chain != NULL && !strcmp(chain->Name(), parent->Chain()->Name()))
|
|
break;
|
|
chain = NULL;
|
|
}
|
|
if (chain != NULL)
|
|
{
|
|
// found mail chain! let's check for the POP3 protocol
|
|
BMessage msg;
|
|
entry_ref ref;
|
|
if (chain->GetFilter(0, &msg, &ref) >= B_OK)
|
|
{
|
|
BPath path(&ref);
|
|
if (path.InitCheck() >= B_OK && !strcmp(path.Leaf(), "POP3"))
|
|
{
|
|
// protocol matches, go execute it!
|
|
|
|
image_id image = load_add_on(path.Path());
|
|
|
|
fLog = "Cannot load POP3 add-on";
|
|
if (image >= B_OK)
|
|
{
|
|
BMailFilter *(* instantiate)(BMessage *, BMailChainRunner *);
|
|
status_t status = get_image_symbol(image, "instantiate_mailfilter",
|
|
B_SYMBOL_TYPE_TEXT, (void **)&instantiate);
|
|
if (status >= B_OK)
|
|
{
|
|
msg.AddInt32("chain", chain->ID());
|
|
msg.AddBool("login_and_do_nothing_else_of_any_importance",true);
|
|
|
|
// instantiating and deleting should be enough
|
|
BMailFilter *filter = (*instantiate)(&msg, runner);
|
|
delete filter;
|
|
}
|
|
else
|
|
fLog = "Cannot run POP3 add-on, symbol not found";
|
|
|
|
unload_add_on(image);
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
fLog = "Could not get inbound protocol";
|
|
}
|
|
else
|
|
fLog = "Cannot find inbound chain";
|
|
|
|
for (int i = chains.CountItems(); i-- > 0;)
|
|
{
|
|
chain = (BMailChain *)chains.ItemAt(i);
|
|
delete chain;
|
|
}
|
|
|
|
return B_ERROR;
|
|
}
|
|
|
|
|
|
status_t
|
|
SMTPProtocol::Login(const char *_login, const char *password)
|
|
{
|
|
if (fAuthType == 0)
|
|
return B_OK;
|
|
|
|
const char *login = _login;
|
|
char hex_digest[33];
|
|
BString out;
|
|
|
|
int32 loginlen = ::strlen(login);
|
|
int32 passlen = ::strlen(password);
|
|
|
|
if (fAuthType & DIGEST_MD5) {
|
|
//******* DIGEST-MD5 Authentication ( tested. works fine [with Cyrus SASL] )
|
|
// this implements only the subpart of DIGEST-MD5 which is
|
|
// required for authentication to SMTP-servers. Integrity-
|
|
// and confidentiality-protection are not implemented, as
|
|
// they are provided by the use of OpenSSL.
|
|
SendCommand("AUTH DIGEST-MD5"CRLF);
|
|
const char *res = fLog.String();
|
|
|
|
if (strncmp(res, "334", 3) != 0)
|
|
return B_ERROR;
|
|
int32 baselen = ::strlen(&res[4]);
|
|
char *base = new char[baselen+1];
|
|
baselen = ::decode_base64(base, &res[4], baselen);
|
|
base[baselen] = '\0';
|
|
|
|
D(bug("base: %s\n", base));
|
|
|
|
map<BString,BString> challengeMap;
|
|
SplitChallengeIntoMap(base, challengeMap);
|
|
|
|
BString rawResponse = BString("username=") << '"' << login << '"';
|
|
rawResponse << ",realm=" << '"' << challengeMap["realm"] << '"';
|
|
rawResponse << ",nonce=" << '"' << challengeMap["nonce"] << '"';
|
|
rawResponse << ",nc=00000001";
|
|
char temp[33];
|
|
for( int i=0; i<32; ++i)
|
|
temp[i] = 1+(rand()%254);
|
|
temp[33] = '\0';
|
|
BString rawCnonce(temp);
|
|
BString cnonce;
|
|
char* cnoncePtr = cnonce.LockBuffer(rawCnonce.Length()*2);
|
|
baselen = ::encode_base64(cnoncePtr, rawCnonce.String(), rawCnonce.Length(), true /* headerMode */);
|
|
cnoncePtr[baselen] = '\0';
|
|
cnonce.UnlockBuffer(baselen);
|
|
rawResponse << ",cnonce=" << '"' << cnonce << '"';
|
|
rawResponse << ",qop=auth";
|
|
BString digestUriValue = BString("smtp/") << fServerName;
|
|
rawResponse << ",digest-uri=" << '"' << digestUriValue << '"';
|
|
char sum[17], hex_digest2[33];
|
|
BString a1,a2,kd;
|
|
BString t1 = BString(login) << ":"
|
|
<< challengeMap["realm"] << ":"
|
|
<< password;
|
|
MD5Sum(sum, (unsigned char*)t1.String(), t1.Length());
|
|
a1 << sum << ":" << challengeMap["nonce"] << ":" << cnonce;
|
|
MD5Digest(hex_digest, (unsigned char*)a1.String(), a1.Length());
|
|
a2 << "AUTHENTICATE:" << digestUriValue;
|
|
MD5Digest(hex_digest2, (unsigned char*)a2.String(), a2.Length());
|
|
kd << hex_digest << ':' << challengeMap["nonce"]
|
|
<< ":" << "00000001" << ':' << cnonce << ':' << "auth"
|
|
<< ':' << hex_digest2;
|
|
MD5Digest(hex_digest, (unsigned char*)kd.String(), kd.Length());
|
|
|
|
rawResponse << ",response=" << hex_digest;
|
|
rawResponse << ",charset=utf-8";
|
|
BString postResponse;
|
|
char *resp = postResponse.LockBuffer(rawResponse.Length() * 2 + 10);
|
|
baselen = ::encode_base64(resp, rawResponse.String(), rawResponse.Length(), true /* headerMode */);
|
|
resp[baselen] = 0;
|
|
postResponse.UnlockBuffer();
|
|
postResponse.Append(CRLF);
|
|
|
|
SendCommand(postResponse.String());
|
|
|
|
res = fLog.String();
|
|
if (atol(res) >= 500)
|
|
return B_ERROR;
|
|
// actually, we are supposed to check the rspauth sent back
|
|
// by the SMTP-server, but that isn't strictly required,
|
|
// so we skip that for now.
|
|
SendCommand(CRLF); // finish off authentication
|
|
res = fLog.String();
|
|
if (atol(res) < 500)
|
|
return B_OK;
|
|
}
|
|
if (fAuthType & CRAM_MD5) {
|
|
//******* CRAM-MD5 Authentication ( tested. works fine [with Cyrus SASL] )
|
|
SendCommand("AUTH CRAM-MD5"CRLF);
|
|
const char *res = fLog.String();
|
|
|
|
if (strncmp(res, "334", 3) != 0)
|
|
return B_ERROR;
|
|
int32 baselen = ::strlen(&res[4]);
|
|
char *base = new char[baselen+1];
|
|
baselen = ::decode_base64(base, &res[4], baselen);
|
|
base[baselen] = '\0';
|
|
|
|
D(bug("base: %s\n", base));
|
|
|
|
::MD5HexHmac(hex_digest, (const unsigned char *)base, (int)baselen,
|
|
(const unsigned char *)password, (int)passlen);
|
|
|
|
D(bug("%s\n%s\n", base, hex_digest));
|
|
|
|
delete[] base;
|
|
|
|
BString preResponse, postResponse;
|
|
preResponse = login;
|
|
preResponse << " " << hex_digest << CRLF;
|
|
char *resp = postResponse.LockBuffer(preResponse.Length() * 2 + 10);
|
|
baselen = ::encode_base64(resp, preResponse.String(), preResponse.Length(), true /* headerMode */);
|
|
resp[baselen] = 0;
|
|
postResponse.UnlockBuffer();
|
|
postResponse.Append(CRLF);
|
|
|
|
SendCommand(postResponse.String());
|
|
|
|
res = fLog.String();
|
|
if (atol(res) < 500)
|
|
return B_OK;
|
|
}
|
|
if (fAuthType & LOGIN) {
|
|
//******* LOGIN Authentication ( tested. works fine)
|
|
ssize_t encodedsize; // required by our base64 implementation
|
|
|
|
SendCommand("AUTH LOGIN"CRLF);
|
|
const char *res = fLog.String();
|
|
|
|
if (strncmp(res, "334", 3) != 0)
|
|
return B_ERROR;
|
|
|
|
// Send login name as base64
|
|
char *login64 = new char[loginlen*3 + 6];
|
|
encodedsize = ::encode_base64(login64, (char *)login, loginlen, true /* headerMode */);
|
|
login64[encodedsize] = 0;
|
|
strcat (login64, CRLF);
|
|
SendCommand(login64);
|
|
delete[] login64;
|
|
|
|
res = fLog.String();
|
|
if (strncmp(res,"334",3) != 0)
|
|
return B_ERROR;
|
|
|
|
// Send password as base64
|
|
login64 = new char[passlen*3 + 6];
|
|
encodedsize = ::encode_base64(login64, (char *)password, passlen, true /* headerMode */);
|
|
login64[encodedsize] = 0;
|
|
strcat (login64, CRLF);
|
|
SendCommand(login64);
|
|
delete[] login64;
|
|
|
|
res = fLog.String();
|
|
if (atol(res) < 500)
|
|
return B_OK;
|
|
}
|
|
if (fAuthType & PLAIN) {
|
|
//******* PLAIN Authentication ( tested. works fine [with Cyrus SASL] )
|
|
// format is:
|
|
// authenticateID + \0 + username + \0 + password
|
|
// (where authenticateID is always empty !?!)
|
|
BString preResponse, postResponse;
|
|
char *stringPntr;
|
|
ssize_t encodedLength;
|
|
stringPntr = preResponse.LockBuffer(loginlen + passlen + 3);
|
|
// +3 to make room for the two \0-chars between the tokens and
|
|
// the final delimiter added by sprintf().
|
|
sprintf (stringPntr, "%c%s%c%s", 0, login, 0, password);
|
|
preResponse.UnlockBuffer(loginlen + passlen + 2);
|
|
// +2 in order to leave out the final delimiter (which is not part
|
|
// of the string).
|
|
stringPntr = postResponse.LockBuffer(preResponse.Length() * 3);
|
|
encodedLength = ::encode_base64(stringPntr, preResponse.String(),
|
|
preResponse.Length(), true /* headerMode */);
|
|
stringPntr[encodedLength] = 0;
|
|
postResponse.UnlockBuffer();
|
|
postResponse.Prepend("AUTH PLAIN ");
|
|
postResponse << CRLF;
|
|
|
|
SendCommand(postResponse.String());
|
|
|
|
const char *res = fLog.String();
|
|
if (atol(res) < 500)
|
|
return B_OK;
|
|
}
|
|
return B_ERROR;
|
|
}
|
|
|
|
|
|
void
|
|
SMTPProtocol::Close()
|
|
{
|
|
|
|
BString cmd = "QUIT";
|
|
cmd += CRLF;
|
|
|
|
if (SendCommand(cmd.String()) != B_OK) {
|
|
// Error
|
|
}
|
|
|
|
#ifdef USESSL
|
|
if (use_ssl) {
|
|
if (ssl)
|
|
SSL_shutdown(ssl);
|
|
if (ctx)
|
|
SSL_CTX_free(ctx);
|
|
}
|
|
#endif
|
|
|
|
#ifdef BONE
|
|
close(_fd);
|
|
#else
|
|
closesocket(_fd);
|
|
#endif
|
|
}
|
|
|
|
|
|
/** Send mail */
|
|
|
|
status_t
|
|
SMTPProtocol::Send(const char *to, const char *from, BPositionIO *message)
|
|
{
|
|
BString cmd = from;
|
|
cmd.Remove(0, cmd.FindFirst("\" <") + 2);
|
|
cmd.Prepend("MAIL FROM: ");
|
|
cmd += CRLF;
|
|
if (SendCommand(cmd.String()) != B_OK)
|
|
return B_ERROR;
|
|
|
|
int32 len = strlen(to);
|
|
BString addr("");
|
|
for (int32 i = 0;i < len;i++) {
|
|
char c = to[i];
|
|
if (c != ',')
|
|
addr += (char)c;
|
|
if (c == ','||i == len-1) {
|
|
if(addr.Length() == 0)
|
|
continue;
|
|
cmd = "RCPT TO: ";
|
|
cmd << addr.String() << CRLF;
|
|
if (SendCommand(cmd.String()) != B_OK)
|
|
return B_ERROR;
|
|
|
|
addr ="";
|
|
}
|
|
}
|
|
|
|
cmd = "DATA";
|
|
cmd += CRLF;
|
|
if (SendCommand(cmd.String()) != B_OK)
|
|
return B_ERROR;
|
|
|
|
// Send the message data. Convert lines starting with a period to start
|
|
// with two periods and so on. The actual sequence is CR LF Period. The
|
|
// SMTP server will remove the periods. Of course, the POP server may then
|
|
// add some of its own, but the POP client should take care of them.
|
|
|
|
ssize_t amountRead;
|
|
ssize_t amountToRead;
|
|
ssize_t amountUnread;
|
|
ssize_t bufferLen = 0;
|
|
const int bufferMax = 2000;
|
|
bool foundCRLFPeriod;
|
|
int i;
|
|
bool messageEndedWithCRLF = false;
|
|
|
|
message->Seek(0, SEEK_END);
|
|
amountUnread = message->Position();
|
|
message->Seek(0, SEEK_SET);
|
|
char *data = new char[bufferMax];
|
|
|
|
while (true) {
|
|
// Fill the buffer if it is getting low, but not every time, to avoid
|
|
// small reads.
|
|
if (bufferLen < bufferMax / 2) {
|
|
amountToRead = bufferMax - bufferLen;
|
|
if (amountToRead > amountUnread)
|
|
amountToRead = amountUnread;
|
|
if (amountToRead > 0) {
|
|
amountRead = message->Read (data + bufferLen, amountToRead);
|
|
if (amountRead <= 0 || amountRead > amountToRead)
|
|
amountUnread = 0; // Just stop reading when an error happens.
|
|
else {
|
|
amountUnread -= amountRead;
|
|
bufferLen += amountRead;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look for the next CRLFPeriod triple.
|
|
foundCRLFPeriod = false;
|
|
for (i = 0; i <= bufferLen - 3; i++) {
|
|
if (data[i] == '\r' && data[i+1] == '\n' && data[i+2] == '.') {
|
|
foundCRLFPeriod = true;
|
|
// Send data up to the CRLF, and include the period too.
|
|
#ifdef USESSL
|
|
if (use_ssl) {
|
|
if (SSL_write(ssl,data,i + 3) < 0) {
|
|
amountUnread = 0; // Stop when an error happens.
|
|
bufferLen = 0;
|
|
break;
|
|
}
|
|
} else
|
|
#endif
|
|
if (send (_fd,data, i + 3,0) < 0) {
|
|
amountUnread = 0; // Stop when an error happens.
|
|
bufferLen = 0;
|
|
break;
|
|
}
|
|
runner->ReportProgress (i + 2 /* Don't include the double period here */,0);
|
|
// Move the data over in the buffer, but leave the period there
|
|
// so it gets sent a second time.
|
|
memmove(data, data + (i + 2), bufferLen - (i + 2));
|
|
bufferLen -= i + 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundCRLFPeriod) {
|
|
if (amountUnread <= 0) { // No more data, all we have is in the buffer.
|
|
if (bufferLen > 0) {
|
|
#ifdef USESSL
|
|
if (use_ssl)
|
|
SSL_write(ssl,data,bufferLen);
|
|
else
|
|
#endif
|
|
send (_fd,data, bufferLen,0);
|
|
runner->ReportProgress (bufferLen,0);
|
|
if (bufferLen >= 2)
|
|
messageEndedWithCRLF = (data[bufferLen-2] == '\r' &&
|
|
data[bufferLen-1] == '\n');
|
|
}
|
|
break; // Finished!
|
|
}
|
|
|
|
// Send most of the buffer, except a few characters to overlap with
|
|
// the next read, in case the CRLFPeriod is split between reads.
|
|
if (bufferLen > 3) {
|
|
#ifdef USESSL
|
|
if (use_ssl) {
|
|
if (SSL_write(ssl,data,bufferLen - 3) < 0)
|
|
break;
|
|
} else
|
|
#endif
|
|
if (send (_fd,data, bufferLen - 3,0) < 0)
|
|
break; // Stop when an error happens.
|
|
runner->ReportProgress (bufferLen - 3,0);
|
|
memmove (data, data + bufferLen - 3, 3);
|
|
bufferLen = 3;
|
|
}
|
|
}
|
|
}
|
|
delete [] data;
|
|
|
|
if (messageEndedWithCRLF)
|
|
cmd = "."CRLF; // The standard says don't add extra CRLF.
|
|
else
|
|
cmd = CRLF"."CRLF;
|
|
|
|
if (SendCommand(cmd.String()) != B_OK)
|
|
return B_ERROR;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
// Receives response from server.
|
|
|
|
int32
|
|
SMTPProtocol::ReceiveResponse(BString &out)
|
|
{
|
|
out = "";
|
|
int32 len = 0,r;
|
|
char buf[SMTP_RESPONSE_SIZE];
|
|
bigtime_t timeout = 1000000*180; // timeout 180 secs
|
|
bool gotCode = false;
|
|
int32 errCode;
|
|
BString searchStr = "";
|
|
|
|
struct timeval tv;
|
|
struct fd_set fds;
|
|
|
|
tv.tv_sec = long(timeout / 1e6);
|
|
tv.tv_usec = long(timeout-(tv.tv_sec * 1e6));
|
|
|
|
/* Initialize (clear) the socket mask. */
|
|
FD_ZERO(&fds);
|
|
|
|
/* Set the socket in the mask. */
|
|
FD_SET(_fd, &fds);
|
|
int result = -1;
|
|
#ifdef USESSL
|
|
if ((use_ssl) && (SSL_pending(ssl)))
|
|
result = 1;
|
|
else
|
|
#endif
|
|
result = select(32, &fds, NULL, NULL, &tv);
|
|
if (result < 0)
|
|
return errno;
|
|
|
|
if (result > 0) {
|
|
while (1) {
|
|
#ifdef USESSL
|
|
if (use_ssl)
|
|
r = SSL_read(ssl,buf,SMTP_RESPONSE_SIZE - 1);
|
|
else
|
|
#endif
|
|
r = recv(_fd,buf, SMTP_RESPONSE_SIZE - 1,0);
|
|
if (r <= 0)
|
|
break;
|
|
|
|
if (!gotCode)
|
|
{
|
|
if (buf[3] == ' ' || buf[3] == '-')
|
|
{
|
|
errCode = atol(buf);
|
|
gotCode = true;
|
|
searchStr << errCode << ' ';
|
|
}
|
|
}
|
|
|
|
len += r;
|
|
out.Append(buf, r);
|
|
|
|
if (strstr(buf, CRLF) && (out.FindFirst(searchStr) != B_ERROR))
|
|
break;
|
|
}
|
|
} else
|
|
fLog = "SMTP socket timeout.";
|
|
|
|
D(bug("S:%s\n", out.String()));
|
|
return len;
|
|
}
|
|
|
|
|
|
// Sends SMTP command. Result kept in fLog
|
|
|
|
status_t
|
|
SMTPProtocol::SendCommand(const char *cmd)
|
|
{
|
|
D(bug("C:%s\n", cmd));
|
|
|
|
#ifdef USESSL
|
|
if (use_ssl) {
|
|
if (SSL_write(ssl,cmd,::strlen(cmd)) < 0)
|
|
return B_ERROR;
|
|
} else
|
|
#endif
|
|
if (send(_fd,cmd, ::strlen(cmd),0) < 0)
|
|
return B_ERROR;
|
|
|
|
fLog = "";
|
|
|
|
// Receive
|
|
while (1) {
|
|
int32 len = ReceiveResponse(fLog);
|
|
|
|
if (len <= 0) {
|
|
D(bug("SMTP: len == %ld\n", len));
|
|
return B_ERROR;
|
|
}
|
|
|
|
if (fLog.Length() > 4 && (fLog[3] == ' ' || fLog[3] == '-'))
|
|
{
|
|
int32 num = atol(fLog.String());
|
|
D(bug("ReplyNumber: %ld\n", num));
|
|
|
|
if (num >= 500)
|
|
return B_ERROR;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
// Instantiate hook
|
|
BMailFilter *
|
|
instantiate_mailfilter(BMessage *settings, BMailChainRunner *status)
|
|
{
|
|
return new SMTPProtocol(settings, status);
|
|
}
|
|
|
|
|
|
// Configuration interface
|
|
BView *
|
|
instantiate_config_panel(BMessage *settings, BMessage *)
|
|
{
|
|
BMailProtocolConfigView *view = new BMailProtocolConfigView(B_MAIL_PROTOCOL_HAS_AUTH_METHODS | B_MAIL_PROTOCOL_HAS_USERNAME | B_MAIL_PROTOCOL_HAS_PASSWORD | B_MAIL_PROTOCOL_HAS_HOSTNAME
|
|
|
|
#ifdef USESSL
|
|
| B_MAIL_PROTOCOL_HAS_FLAVORS);
|
|
view->AddFlavor("Unencrypted");
|
|
view->AddFlavor("SSL");
|
|
#else
|
|
);
|
|
#endif
|
|
|
|
view->AddAuthMethod(MDR_DIALECT_CHOICE ("None","無し"), false);
|
|
view->AddAuthMethod(MDR_DIALECT_CHOICE ("ESMTP","ESMTP"));
|
|
view->AddAuthMethod(MDR_DIALECT_CHOICE ("POP3 before SMTP","送信前に受信する"), false);
|
|
|
|
BTextControl *control = (BTextControl *)(view->FindView("host"));
|
|
control->SetLabel(MDR_DIALECT_CHOICE ("SMTP Host: ","SMTPサーバ: "));
|
|
//control->SetDivider(be_plain_font->StringWidth("SMTP Host: "));
|
|
|
|
view->SetTo(settings);
|
|
|
|
return view;
|
|
}
|