Peter Bowen wrote:
I realize that SASL mechanisms are not directly in the domain of this
group, but I'm hoping I might get pointed the right way.
I've been working on implementing a framework for building SMTP
servers in ruby (https://github.com/pzb/groat-smtpd). One of the
issues I've run into several times is interoperability of the LOGIN
mechanism with various clients. The latest problem is certain
Microsoft SMTP implementations that send an initial client response,
and have major issues when it is not accepted. Is there any reliable
specification for the LOGIN mechanism and/or how it is used with SMTP?
Thanks,
Peter
Hi Peter,
Is there an specific example of the problem to get an idea of what you
seeing?
The specification for SASL is pretty straight forward. The documents
to read should be:
RFC4422 Simple Authentication and Security Layer (SASL)
RFC2554 SMTP Service Extension for Authentication
RFC2487 SMTP Service Extension for Secure SMTP over TLS
RFC4409 Message Submission for Mail
But there are a few something you need to know that you may come
across outside the docs or not obvious.
One specifically for LOGIN method is to display it twice like so:
C: EHLO client.domain.com
S: 250-server.com, whats up!
S: 250-SIZE 10240000
S: 250-AUTH CRAM-MD5 LOGIN DIGEST-MD5
S: 250-AUTH=LOGIN
S: 250 HELP
Some older clients look for that AUTH=LOGIN.
Also, LOGIN can be done in two forms
AUTH LOGIN [base64_username]
AUTH LOGIN
So your server needs to be ready for optional data.
Also, for the handshaking exchange, due to various client behavior
(mostly cell I found), some pump multiple lines and if the server need
to properly parse it on a per line basis (i.e. the next line reading
comes off an input cache)
Finally, if you are just looking for LOGIN, which should be the
minimum method, its pretty simple. Here is some (older version( C/C++
code for the AUTH command for the LOGIN method only. I pulled out the
newer multi SASL methods version that interacts with a backend server
in the handshake.
BOOL TSMTPServer::AUTH(char *args)
{
if (!IsServiceAvailable()) return TRUE;
if (!CheckTLS())return TRUE;
if (!IsExtendedSmtp) {
Send("500 AUTH not enabled at server!\r\n");
return TRUE;
}
if (authattempts > 3) {
Send("503 Too many AUTH attempts!\r\n");
return TRUE;
}
if (IsUserAuthorized) {
Send("503 Invalid AUTH request. Already Authenticated\r\n");
return TRUE;
}
// Support for NON-RFC AUTH=LOGIN
// - Designed by NETSCAPE, SUPPORTED by MICROSOFT
// - supported by at least NS4, NS4.5, OL4, OL5
// - optional: AUTH LOGIN [base64_username]
// - optional: AUTH LOGIN
char buf[1024];
ZeroMemory(&username,sizeof(username));
ZeroMemory(&password,sizeof(password));
if (!strnicmp(args, "LOGIN", 5)) {
int received = 0;
int sockerr = 0;
ZeroMemory(&buf,sizeof(buf));
//----------------------------------------------------------
// Get USERNAME
//----------------------------------------------------------
// Some clients will provide the BASE64 username as part
// of the LOGIN argument. Otherwise, ask for the username.
if (strlen(args) > 6) {
strcpy(buf, args+6);
UnBase64(username, buf);
}
else {
Send("334 VXNlcm5hbWU6====\r\n");
ZeroMemory(&buf,sizeof(buf));
received = Control->GetBlock(buf,sizeof(buf)-1);
sockerr = GetLastError();
if ((received <= 0) && (sockerr != 0)) {
Send("454 Failure to receive AUTH USERNAME. Error
%d\r\n",sockerr);
return TRUE;
}
if (received <= 0) {
Send("535 Authorization Failed, No Username!\r\n");
authattempts++;
return TRUE;
}
UnBase64(username, buf);
// 450.3b7 - just in case it was encoded
if (!strcmp(buf,"*") || !strcmp(username,"*")) {
Send("501 Authorization cancellation accepted\r\n");
authattempts++;
return TRUE;
}
//
} // user name
//----------------------------------------------------------
// Get PASSWORD
//----------------------------------------------------------
Send("334 UGFzc3dvcmQ6====\r\n");
ZeroMemory(&buf,sizeof(buf));
received = Control->GetBlock(buf,sizeof(buf)-1);
sockerr = GetLastError();
if ((received <= 0) && (sockerr != 0)) {
Send("454 Failure to receive AUTH PASSWORD. Error
%d\r\n",sockerr);
return TRUE;
}
if (received <= 0) {
Send("535 Authorization Failed, No Password!\r\n");
authattempts++;
return TRUE;
}
TrimCrLf(buf);
UnBase64(password, buf);
if (!strcmp(buf,"*") || !strcmp(password,"*")) {
Send("501 Authorization cancellation accepted\r\n");
authattempts++;
return TRUE;
}
IsUserAuthorized = YourServerSideLogin(username,password);
if (!IsUserAuthorized)
{
int err = GetLastError();
switch (err) {
case WC_USER_NOT_FOUND:
Send("535 Authentication Failure: User \"%s\"
Unknown!\r\n",username);
break;
case WC_INCORRECT_PASSWORD:
Send("535 Authentication Failure: Invalid
Password!\r\n");
break;
case WC_ACCESS_DENIED:
Send("535 Authentication Failure: Email Access
Denied!\r\n");
break;
default:
Send("535 System Authentication Failure %8X!\r\n",err);
break;
}
authattempts++;
InterlockedIncrement(&smtpstats.ConnectRejects);
return TRUE;
}
Send("235 Authentication successful\r\n");
return TRUE;
}
Send("504 Unrecognized authentication type: %s.\r\n",args);
authattempts++;
return TRUE;
}
Hope this helps
--
Sincerely
Hector Santos
http://www.santronics.com