/*
* ratSMTP.c --
*
* This file contains basic support for sending messages via SMTP.
*
* TkRat software and its included text is Copyright 1996-2002 by
* Martin Forssén
*
* The full text of the legal notice is contained in the file called
* COPYRIGHT, included with this distribution.
*/
#include "rat.h"
/*
* Each channel has one of these structures. The channel handler is actually
* just a pointer to this structure.
*/
typedef struct SMTPChannelPriv {
Tcl_Channel channel;
unsigned int mime : 1; /* True if the peer supports 8 bit mime */
unsigned int dsn : 1; /* True if the peer supports DSN */
} SMTPChannelPriv;
/*
* Linked list of cached channels
*/
typedef struct ChannelCache {
SMTPChannelPriv *chPtr;
char *host;
int port;
struct ChannelCache *next;
} ChannelCache;
static ChannelCache *channelCache = NULL;
/*
* Local functions
*/
static char *RatTimedGets(Tcl_Interp *interp, Tcl_Channel channel,int timeout);
static int RatSendCommand(Tcl_Interp *interp, Tcl_Channel channel, char *cmd);
static int RatSendRcpt(Tcl_Interp *interp, Tcl_Channel channel,
ADDRESS *adrPtr, DSNhandle handle, int verbose);
/*
*----------------------------------------------------------------------
*
* RatTimedGets --
*
* A gets with timeout. the channel must be in nonblocking mode.
*
* Results:
* Returns a pointer to a static area containing the read string.
*
* Side effects:
* The previous result is overwritten
*
*
*----------------------------------------------------------------------
*/
static char*
RatTimedGets(Tcl_Interp *interp, Tcl_Channel channel, int timeout)
{
static Tcl_DString ds;
static int dsInit = 0;
if (!dsInit) {
dsInit = 1;
Tcl_DStringInit(&ds);
} else {
Tcl_DStringSetLength(&ds, 0);
}
Tcl_SetChannelOption(interp, channel, "-blocking", "0");
while (-1 == Tcl_Gets(channel, &ds)) {
if (Tcl_InputBlocked(channel) && timeout) {
sleep(1),
timeout--;
} else {
Tcl_SetChannelOption(interp, channel, "-blocking", "1");
return NULL;
}
}
Tcl_SetChannelOption(interp, channel, "-blocking", "1");
return Tcl_DStringValue(&ds);
}
/*
*----------------------------------------------------------------------
*
* RatSendCommand --
*
* Send a command to the SMTP peer, wait for and parse the result.
*
* Results:
* A standard Tcl result and an eventual error messages in
* the result area.
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
int
RatSendCommand(Tcl_Interp *interp, Tcl_Channel channel, char *cmd)
{
int result, timeout;
Tcl_Obj *oPtr;
char *reply;
Tcl_Write(channel, cmd, -1);
if ('\n' != cmd[strlen(cmd)-1]) {
Tcl_Write(channel, "\r\n", -1);
}
oPtr = Tcl_GetVar2Ex(interp, "option", "smtp_timeout", TCL_GLOBAL_ONLY);
Tcl_GetIntFromObj(interp, oPtr, &timeout);
do {
reply = RatTimedGets(interp, channel, timeout);
if (reply) {
if ('2' == reply[0] || '3' == reply[0]) {
result = TCL_OK;
} else {
Tcl_SetResult(interp, reply, TCL_VOLATILE);
result = TCL_ERROR;
}
} else {
Tcl_SetResult(interp, "Timeout from SMTP server", TCL_STATIC);
result = TCL_ERROR;
}
} while (TCL_OK == result && '-' == reply[4]);
return result;
}
/*
*----------------------------------------------------------------------
*
* RatSendRcpt --
*
* Send the RCPT TO statements to the SMTP peer.
*
* Results:
* Returns the number of failed addresses. The result string of interp
* will contain more information in case of failure.
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
static int
RatSendRcpt(Tcl_Interp *interp, Tcl_Channel channel, ADDRESS *adrPtr,
DSNhandle handle, int verbose)
{
char buf[2048], adr[1024];
unsigned char *cPtr;
int failures = 0, i;
for (; adrPtr; adrPtr = adrPtr->next) {
if (RatAddressSize(adrPtr, 0) > sizeof(adr)) {
RatLogF(interp, RAT_WARN, "ridiculously_long", RATLOG_TIME);
failures++;
}
adr[0] = '\0';
rfc822_address(adr, adrPtr);
snprintf(buf, sizeof(buf), "RCPT TO:<%s>", adr);
if (handle) {
RatDSNAddRecipient(interp, handle, adr);
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
" NOTIFY=SUCCESS,FAILURE,DELAY");
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
" ORCPT=rfc822;");
for (i = strlen(buf),cPtr = (unsigned char*)adr; *cPtr; cPtr++) {
if (*cPtr < 33 || *cPtr > 126 || *cPtr == '+' || *cPtr == '='){
buf[i++] = '+';
buf[i++] = alphabetHEX[*cPtr>>4];
buf[i++] = alphabetHEX[*cPtr&0xf];
} else {
buf[i++] = *cPtr;
}
}
buf[i] = '\0';
}
if (3 == verbose) {
RatLogF(interp, RAT_PARSE, "send_rcpt", RATLOG_EXPLICIT, adr);
}
if (TCL_OK != RatSendCommand(interp, channel, buf)) {
failures++;
}
}
return failures;
}
/*
*----------------------------------------------------------------------
*
* RatSMTPOpen --
*
* Open an SMTP channel.
*
* Results:
* Returns a channel handler.
*
* Side effects:
* A new channel is created.
*
*
*----------------------------------------------------------------------
*/
SMTPChannel
RatSMTPOpen (Tcl_Interp *interp, char *host, int verbose, const char *role)
{
SMTPChannelPriv *chPtr;
char *reply, buf[1024], *cPtr, *ch;
int port, timeout;
ChannelCache *cachePtr;
Tcl_Obj *oPtr;
strlcpy(buf, host, sizeof(buf));
if ((cPtr = strchr(buf, ':'))) {
*cPtr++ = '\0';
port = atoi(cPtr);
} else {
port = 25; /* The default SMTP port */
}
for (cachePtr = channelCache; cachePtr; cachePtr = cachePtr->next) {
if (!strcmp(cachePtr->host, buf) && cachePtr->port == port) {
if (TCL_OK == RatSendCommand(interp, cachePtr->chPtr->channel,
"RSET")) {
return cachePtr->chPtr;
}
break;
}
}
if (verbose > 1) {
RatLogF(interp, RAT_PARSE, "opening_connection", RATLOG_EXPLICIT);
}
chPtr = (SMTPChannelPriv*)ckalloc(sizeof(SMTPChannelPriv));
chPtr->mime = chPtr->dsn = 0;
/*
* Your compiler may complain that there are too many arguments to
* Tcl_OpenTcpClient() below. This is a symtom of you having a prerelease
* of tcl7.5 installed. In this case you must upgrade to the real
* releases of tcl7.5/tk4.1 and reconfigure tkrat (this MUST be done).
* After rerunning configure you may build tkrat.
*/
if (NULL == (chPtr->channel =Tcl_OpenTcpClient(interp,port,buf,NULL,0,0))){
ckfree(chPtr);
return NULL;
}
Tcl_SetChannelOption(interp, chPtr->channel, "-buffering", "line");
Tcl_SetChannelOption(interp, chPtr->channel, "-translation", "binary");
/*
* Get initial greeting
*/
if (verbose > 1) {
RatLogF(interp, RAT_PARSE, "wait_greeting", RATLOG_EXPLICIT);
}
oPtr = Tcl_GetVar2Ex(interp, "option", "smtp_timeout", TCL_GLOBAL_ONLY);
Tcl_GetIntFromObj(interp, oPtr, &timeout);
do {
reply = RatTimedGets(interp, chPtr->channel, timeout);
if (!reply || '2' != reply[0]) {
Tcl_Close(interp, chPtr->channel);
ckfree(chPtr);
return NULL;
}
} while (strncmp("220 ", reply, 4));
/*
* Send EHLO (HELO) and get capabilities
*/
if (verbose > 1) {
RatLogF(interp, RAT_PARSE, "get_capabilities", RATLOG_EXPLICIT);
}
ch = RatGetCurrent(interp, RAT_HOST, role);
snprintf(buf, sizeof(buf), "EHLO %s\r\n", ch);
Tcl_Write(chPtr->channel, buf, -1);
reply = RatTimedGets(interp, chPtr->channel, timeout);
if (!reply || '2' != reply[0]) {
snprintf(buf, sizeof(buf), "HELO %s\r\n", ch);
Tcl_Write(chPtr->channel, buf, -1);
reply = RatTimedGets(interp, chPtr->channel, timeout);
}
while (reply) {
if (!reply) {
Tcl_Close(interp, chPtr->channel);
ckfree(chPtr);
return NULL;
}
if (!strncmp("8BITMIME", &reply[4], 8)) {
chPtr->mime = 1;
} else if (!strncmp("DSN", &reply[4], 3)) {
chPtr->dsn = 1;
}
if (!strncmp("250 ", reply, 4)) {
break;
}
reply = RatTimedGets(interp, chPtr->channel, timeout);
}
if (verbose > 1) {
RatLog(interp, RAT_PARSE, "", RATLOG_EXPLICIT);
}
cachePtr = (ChannelCache*)ckalloc(sizeof(ChannelCache)+strlen(host)+1);
cachePtr->chPtr = chPtr;
cachePtr->host = (char*)cachePtr + sizeof(*cachePtr);
strlcpy(cachePtr->host, host, strlen(host));
cachePtr->port = port;
cachePtr->next = channelCache;
channelCache = cachePtr;
return (SMTPChannel)chPtr;
}
/*
*----------------------------------------------------------------------
*
* RatSMTPClose --
*
* Close an SMTP channel.
*
* Results:
* None.
*
* Side effects:
* THe chanel is closed.
*
*
*----------------------------------------------------------------------
*/
void
RatSMTPClose (Tcl_Interp *interp, SMTPChannel channel, int verbose)
{
SMTPChannelPriv *chPtr = (SMTPChannelPriv*)channel;
ChannelCache **c1Ptr, *c2Ptr;
/*
* Close connection
*/
if (verbose > 1) {
RatLogF(interp, RAT_PARSE, "closing", RATLOG_EXPLICIT);
}
Tcl_Write(chPtr->channel, "QUIT\r\n", -1);
Tcl_Close(interp, chPtr->channel);
if (verbose > 1) {
RatLog(interp, RAT_PARSE, "", RATLOG_EXPLICIT);
}
ckfree(chPtr);
/*
* Clear cache
*/
for (c1Ptr = &channelCache; *c1Ptr && (*c1Ptr)->chPtr != chPtr;
c1Ptr = &(*c1Ptr)->next);
if (*c1Ptr) {
c2Ptr = (*c1Ptr)->next;
ckfree(*c1Ptr);
*c1Ptr = c2Ptr;
}
}
/*
*----------------------------------------------------------------------
*
* RatSMTPClose --
*
* Close an SMTP channel.
*
* Results:
* None.
*
* Side effects:
* THe chanel is closed.
*
*
*----------------------------------------------------------------------
*/
void
RatSMTPCloseAll (Tcl_Interp *interp, int verbose)
{
while (channelCache) {
RatSMTPClose(interp, channelCache->chPtr, verbose);
}
}
/*
*----------------------------------------------------------------------
*
* RatSMTPSend --
*
* Send a message with SMTP over the specified channel.
*
* Results:
* A standard Tcl result and an eventual error messages in
* the result area.
*
* Side effects:
* The DSN structures may be updated.
*
*
*----------------------------------------------------------------------
*/
int
RatSMTPSend (Tcl_Interp *interp, SMTPChannel channel, ENVELOPE *envPtr,
BODY *bodyPtr, int doDSN, int verbose)
{
SMTPChannelPriv *chPtr = (SMTPChannelPriv*)channel;
char buf[1024], *header;
int failures = 0;
DSNhandle handle = NULL;
/*
* Check input and reset stream to known status.
*/
if (!(envPtr->to || envPtr->cc || envPtr->bcc)) {
Tcl_SetResult(interp, "No recipients specified", TCL_STATIC);
goto abort;
}
if (TCL_OK != RatSendCommand(interp, chPtr->channel, "RSET")) {
goto abort;
}
/*
* Check if we should request DSN
*/
if (doDSN && !chPtr->dsn) {
RatLogF(interp, RAT_WARN, "no_dsn", RATLOG_TIME);
doDSN = 0;
}
/*
* Send envelope information.
*/
if (verbose > 1) {
if (verbose == 2) {
RatLogF(interp, RAT_PARSE, "send_envelope", RATLOG_EXPLICIT);
} else {
RatLogF(interp, RAT_PARSE, "send_from", RATLOG_EXPLICIT);
}
}
if (RatAddressSize(envPtr->from, 0) > sizeof(buf)-128) {
RatLogF(interp, RAT_WARN, "ridiculously_long", RATLOG_TIME);
goto abort;
}
snprintf(buf, sizeof(buf), "MAIL FROM:<");
rfc822_address(buf, envPtr->from);
strlcat(buf, ">", sizeof(buf));
if (chPtr->mime) {
strlcat(buf, " BODY=8BITMIME", sizeof(buf));
}
if (doDSN) {
RatGenId(NULL, interp, 0, NULL);
handle = RatDSNStartMessage(interp, Tcl_GetStringResult(interp),
envPtr->subject);
strlcat(buf, " ENVID=", sizeof(buf));
strlcat(buf, Tcl_GetStringResult(interp), sizeof(buf));
}
if (TCL_OK != RatSendCommand(interp, chPtr->channel, buf)) {
goto abort;
}
failures += RatSendRcpt(interp, chPtr->channel,envPtr->to,handle,verbose);
failures += RatSendRcpt(interp, chPtr->channel,envPtr->cc,handle,verbose);
failures += RatSendRcpt(interp, chPtr->channel,envPtr->bcc,handle,verbose);
if (failures) {
goto abort;
}
/*
* Send message data
*/
if (verbose > 1) {
RatLogF(interp, RAT_PARSE, "send_data", RATLOG_EXPLICIT);
}
if (TCL_OK != RatSendCommand(interp, chPtr->channel, "DATA")) {
goto abort;
}
header = (char*)ckalloc(RatHeaderSize(envPtr, bodyPtr));
rfc822_output(header, envPtr, bodyPtr, RatTclPutsSMTP, chPtr->channel,
chPtr->mime);
ckfree(header);
if (verbose > 1) {
RatLogF(interp, RAT_PARSE, "wait_ack", RATLOG_EXPLICIT);
}
if (TCL_OK != RatSendCommand(interp, chPtr->channel, ".")) {
goto abort;
}
if (handle) {
RatDSNFinish(interp, handle);
}
return TCL_OK;
abort:
RatDSNAbort(interp, handle);
return TCL_ERROR;
}
/*
*----------------------------------------------------------------------
*
* RatSMTPSupportDSN --
*
* Check if a host supports DSN.
*
* Results:
* TCL_OK and the result area will contain "1" if the host supports
* DSN and "0" otherwise.
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
int
RatSMTPSupportDSN(ClientData dummy, Tcl_Interp *interp, int objc,
Tcl_Obj *const objv[])
{
SMTPChannelPriv *chPrivPtr;
SMTPChannel channel;
int verbose, result;
if (objc != 2) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
Tcl_GetString(objv[0]), " hostname\"",
(char *) NULL);
return TCL_ERROR;
}
Tcl_GetIntFromObj(interp,
Tcl_GetVar2Ex(interp, "option", "smtp_verbose",
TCL_GLOBAL_ONLY),
&verbose);
channel = RatSMTPOpen(interp, Tcl_GetString(objv[1]), verbose, "");
if (channel) {
chPrivPtr = (SMTPChannelPriv*)channel;
result = chPrivPtr->dsn;
RatSMTPClose(interp, channel, verbose);
} else {
result = 0;
}
if (verbose) {
RatLog(interp, RAT_PARSE, "", RATLOG_EXPLICIT);
}
Tcl_SetObjResult(interp, Tcl_NewIntObj(result));
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* RatTclPutsSMTP --
*
* Like RatTclPuts but also escapes sigle dots with double dots.
*
* Results:
* Always returns 1L.
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
long
RatTclPutsSMTP(void *stream_x, char *string)
{
Tcl_Channel channel = (Tcl_Channel)stream_x;
char *cPtr, *srcPtr;
if ('.' == string[0]) {
Tcl_Write(channel, ".", 1);
}
for (srcPtr = string; *srcPtr;) {
if (!srcPtr[0] || !srcPtr[1] || !srcPtr[2]) {
break;
}
for (cPtr = srcPtr; cPtr[2]; cPtr++) {
if ('\r' == cPtr[0] && '\n' == cPtr[1] && '.' == cPtr[2]) {
break;
}
}
if (cPtr[2]) {
if (-1 == Tcl_Write(channel, srcPtr, cPtr-srcPtr+3)
|| -1 == Tcl_Write(channel, ".", 1)) {
return 0;
}
srcPtr = cPtr+3;
} else {
break;
}
}
if (-1 == Tcl_Write(channel, srcPtr, -1)) {
return 0;
} else {
return 1;
}
}
/*
*----------------------------------------------------------------------
*
* RatTclPutsSendmail --
*
* Like RatTclPutsSMTP but also escapes changes line endings to
* put \n
*
* Results:
* Always returns 1L.
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
long
RatTclPutsSendmail(void *stream_x, char *string)
{
Tcl_Channel channel = (Tcl_Channel)stream_x;
char *cPtr, *srcPtr;
int add;
for (srcPtr = string; *srcPtr;) {
if (!srcPtr[0] || !srcPtr[1]) {
break;
}
add = 1;
for (cPtr = srcPtr; cPtr[1]; cPtr++) {
if ('\r' == cPtr[0] && '\n' == cPtr[1]) {
cPtr--;
add = 2;
break;
}
}
if (-1 == Tcl_Write(channel, srcPtr, (cPtr+1)-srcPtr)) {
return 0;
}
srcPtr = cPtr+add;
}
if (*srcPtr && -1 == Tcl_Write(channel, srcPtr, -1)) {
return 0;
} else {
return 1;
}
}
/*
*----------------------------------------------------------------------
*
* RatHeaderSize --
*
* Calculate size of header
*
* Results:
* Maximum size of header
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
static int RatHeaderLineSize(char *name, ENVELOPE *env, char *text);
static int RatHeaderAddressSize(char *name, ENVELOPE *env, ADDRESS *adr);
static int
RatHeaderLineSize(char *name, ENVELOPE *env, char *text)
{
if (text) {
return (env->remail ? 7 : 0) + strlen(name) + 2 + strlen(text) + 2;
} else {
return 0;
}
}
static int
RatHeaderAddressSize(char *name, ENVELOPE *env, ADDRESS *adr)
{
if (adr) {
return (env->remail?7:0) + strlen(name) + 2 + RatAddressSize(adr, 1)+2;
} else {
return 0;
}
}
size_t
RatHeaderSize(ENVELOPE *env,BODY *body)
{
size_t len = 0;
if (env->remail) len += strlen(env->remail);
len += RatHeaderLineSize("Newsgroups", env, env->newsgroups);
len += RatHeaderLineSize("Date", env, env->date);
len += RatHeaderAddressSize("From", env, env->from);
len += RatHeaderAddressSize("Sender", env, env->sender);
len += RatHeaderAddressSize("Reply-To", env, env->reply_to);
len += RatHeaderLineSize("Subject", env, env->subject);
if (env->bcc && !(env->to || env->cc)) {
len += strlen("To: undisclosed recipients: ;\015\012");
}
len += RatHeaderAddressSize("To", env, env->to);
len += RatHeaderAddressSize("cc", env, env->cc);
len += RatHeaderLineSize("In-Reply-To", env, env->in_reply_to);
len += RatHeaderLineSize("Message-ID", env, env->message_id);
len += RatHeaderLineSize("Followup-to", env, env->followup_to);
len += RatHeaderLineSize("References", env, env->references);
if (body && !env->remail) { /* not if remail or no body structure */
/*
* TODO: Fix this correctly
* Here we assume that the body headers will never become longer
* than 8192 bytes
*/
len += 8192;
}
len += 2;
return len;
}
syntax highlighted by Code2HTML, v. 0.9.1