summaryrefslogtreecommitdiff
path: root/lmtp.c
diff options
context:
space:
mode:
Diffstat (limited to 'lmtp.c')
-rw-r--r--lmtp.c717
1 files changed, 717 insertions, 0 deletions
diff --git a/lmtp.c b/lmtp.c
new file mode 100644
index 00000000..3cdf428f
--- /dev/null
+++ b/lmtp.c
@@ -0,0 +1,717 @@
+/* $Id$
+ * (c) 2000-2002 IC&S, The Netherlands
+ *
+ * implementation for lmtp commands according to RFC 1081 */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "dbmail.h"
+#include "lmtp.h"
+#include "pipe.h"
+#include "header.h"
+#include "db.h"
+#include "debug.h"
+#include "dbmailtypes.h"
+#include "auth.h"
+#include "clientinfo.h"
+#include "lmtp.h"
+#ifdef PROC_TITLES
+#include "proctitleutils.h"
+#endif
+
+#define INCOMING_BUFFER_SIZE 512
+
+/* default timeout for server daemon */
+#define DEFAULT_SERVER_TIMEOUT 300
+
+/* max_errors defines the maximum number of allowed failures */
+#define MAX_ERRORS 3
+
+/* max_in_buffer defines the maximum number of bytes that are allowed to be
+ * in the incoming buffer */
+#define MAX_IN_BUFFER 255
+
+/* This one needs global score for bounce.c */
+struct list mimelist;
+
+/* These are needed across multiple calls to lmtp() */
+struct list rcpt, userids, fwds;
+char *envelopefrom = NULL;
+
+/* allowed lmtp commands */
+const char *commands [] =
+{
+ "LHLO", "QUIT", "RSET", "DATA", "MAIL",
+ "VRFY", "EXPN", "HELP", "NOOP", "RCPT"
+};
+
+const char validchars[] =
+"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+"_.!@#$%^&*()-+=~[]{}<>:;\\/ ";
+
+char myhostname[64];
+
+int lmtp_handle_connection(clientinfo_t *ci)
+{
+ /*
+ Handles connection and calls
+ lmtp command handler
+ */
+
+ int done = 1; /* loop state */
+ char *buffer = NULL; /* connection buffer */
+ int cnt; /* counter */
+
+ PopSession_t session; /* current connection session */
+
+ /* setting Session variables */
+ session.error_count = 0;
+
+ session.username = NULL;
+ session.password = NULL;
+
+ session.SessionResult = 0;
+
+ /* reset counters */
+ session.totalsize = 0;
+ session.virtual_totalsize = 0;
+ session.totalmessages = 0;
+ session.virtual_totalmessages = 0;
+
+
+ /* getting hostname */
+ gethostname(myhostname,64);
+ myhostname[63] = 0; /* make sure string is terminated */
+
+ buffer=(char *)my_malloc(INCOMING_BUFFER_SIZE*sizeof(char));
+
+ if (!buffer)
+ {
+ trace(TRACE_MESSAGE,"lmtp_handle_connection(): Could not allocate buffer");
+ return 0;
+ }
+
+ if (ci->tx)
+ {
+ /* sending greeting */
+ fprintf(ci->tx,"220 %s DBMail LMTP service ready to rock\r\n",
+ myhostname);
+ fflush(ci->tx);
+ }
+ else
+ {
+ trace(TRACE_MESSAGE,"lmtp_handle_connection(): TX stream is null!");
+ return 0;
+ }
+
+ while (done > 0)
+ {
+ /* set the timeout counter */
+ alarm(ci->timeout);
+
+ /* clear the buffer */
+ memset(buffer, 0, INCOMING_BUFFER_SIZE);
+
+ for (cnt=0; cnt < INCOMING_BUFFER_SIZE-1; cnt++)
+ {
+ do
+ {
+ clearerr(ci->rx);
+ fread(&buffer[cnt], 1, 1, ci->rx);
+
+ /* leave, an alarm has occured during fread */
+ if (!ci->rx) return 0;
+ } while (ferror(ci->rx) && errno == EINTR);
+
+ if (buffer[cnt] == '\n' || feof(ci->rx) || ferror(ci->rx))
+ {
+ buffer[cnt+1] = '\0';
+ break;
+ }
+ }
+
+ if (feof(ci->rx) || ferror(ci->rx))
+ {
+ /* check client eof */
+ done = -1;
+ }
+ else
+ {
+ /* reset function handle timeout */
+ alarm(0);
+ /* handle lmtp commands */
+ done = lmtp(ci->tx, ci->rx, buffer, ci->ip, &session);
+ }
+ fflush(ci->tx);
+ }
+
+ /* memory cleanup */
+ my_free(buffer);
+ buffer = NULL;
+
+ /* reset timers */
+ alarm(0);
+ __debug_dumpallocs();
+
+ return 0;
+}
+
+
+int lmtp_reset(PopSession_t *session)
+ {
+ /* Free the lists and reinitialize
+ * but only if they were previously
+ * initialized by LMTP_LHLO... */
+ if( session->state == LHLO )
+ {
+ list_freelist( &rcpt.start );
+ list_init( &rcpt );
+ list_freelist( &userids.start );
+ list_init( &userids );
+ list_freelist( &fwds.start );
+ list_init( &fwds );
+ }
+
+ if( envelopefrom != NULL )
+ {
+ my_free( envelopefrom );
+ }
+ envelopefrom = NULL;
+
+ session->state = LHLO;
+
+ return 1;
+ }
+
+
+int lmtp_error(PopSession_t *session, void *stream, const char *formatstring, ...)
+{
+ va_list argp;
+
+ if (session->error_count>=MAX_ERRORS)
+ {
+ trace(TRACE_MESSAGE,"lmtp_error(): too many errors (MAX_ERRORS is %d)",MAX_ERRORS);
+ fprintf((FILE *)stream, "500 Too many errors, closing connection.\r\n");
+ session->SessionResult = 2; /* possible flood */
+ lmtp_reset(session);
+ return -3;
+ }
+ else
+ {
+ va_start(argp, formatstring);
+ vfprintf((FILE *)stream, formatstring, argp);
+ va_end(argp);
+ }
+
+ trace(TRACE_DEBUG,"lmtp_error(): an invalid command was issued");
+ session->error_count++;
+ return 1;
+}
+
+
+int lmtp(void *stream, void *instream, char *buffer, char *client_ip, PopSession_t *session)
+{
+ /* returns values:
+ * 0 to quit
+ * -1 on failure
+ * 1 on success */
+ char *command, *value;
+ int cmdtype;
+ int indx=0;
+
+ /* buffer overflow attempt */
+ if (strlen(buffer) > MAX_IN_BUFFER)
+ {
+ trace(TRACE_DEBUG, "lmtp(): buffer overflow attempt");
+ return -3;
+ }
+
+ /* check for command issued */
+ while (strchr(validchars, buffer[indx]))
+ indx++;
+
+ /* end buffer */
+ buffer[indx]='\0';
+
+ trace(TRACE_DEBUG,"lmtp(): incoming buffer: [%s]",buffer);
+
+ command = buffer;
+
+ value = strstr(command," "); /* look for the separator */
+
+ if (value!=NULL)
+ {
+ *value = '\0'; /* set a \0 on the command end */
+ value++; /* skip space */
+
+ if (strlen(value) == 0)
+ {
+ value=NULL; /* no value specified */
+ }
+ else
+ {
+ trace(TRACE_DEBUG,"lmtp(): command issued :cmd [%s], value [%s]\n",command, value);
+ }
+ }
+
+ for (cmdtype = LMTP_STRT; cmdtype < LMTP_END; cmdtype ++)
+ if (strcasecmp(command, commands[cmdtype]) == 0) break;
+
+ trace(TRACE_DEBUG,"lmtp(): command looked up as commandtype %d", cmdtype);
+
+ /* commands that are allowed to have no arguments */
+ if ((value==NULL) &&
+ !(
+ (cmdtype==LMTP_LHLO) || (cmdtype==LMTP_DATA) ||
+ (cmdtype==LMTP_RSET) || (cmdtype==LMTP_QUIT) ||
+ (cmdtype==LMTP_NOOP) || (cmdtype==LMTP_HELP)
+ ))
+ {
+ return lmtp_error(session, stream, "500 This command requires an argument.\r\n");
+ }
+
+ switch (cmdtype)
+ {
+ case LMTP_QUIT :
+ {
+ fprintf((FILE *)stream, "221 %s BYE\r\n", myhostname);
+ lmtp_reset(session);
+ return 0; /* return 0 to cause the connection to close */
+ }
+ case LMTP_NOOP :
+ {
+ fprintf((FILE *)stream, "250 OK\r\n");
+ return 1;
+ }
+ case LMTP_RSET :
+ {
+ fprintf((FILE *)stream, "250 OK\r\n");
+ lmtp_reset(session);
+ return 1;
+ }
+ case LMTP_LHLO :
+ {
+ /* Reply wth our hostname and a list of features.
+ * The RFC requires a couple of SMTP extensions
+ * with a MUST statement, so just hardcode them.
+ * */
+ fprintf((FILE *)stream,
+ "250-%s\r\n"
+ "250-PIPELINING\r\n"
+ "250-ENHANCEDSTATUSCODES\r\n"
+ /* This is a SHOULD implement:
+ * "250-8BITMIME\r\n"
+ * Might as well do these, too:
+ * "250-CHUNKING\r\n"
+ * "250-BINARYMIME\r\n"
+ * */
+ "250 SIZE\r\n", myhostname );
+ /* Free the recipients list and reinitialize it */
+ // list_freelist( &rcpt.start );
+ list_init( &rcpt );
+ // list_freelist( &userids.start );
+ list_init( &userids );
+ // list_freelist( &fwds.start );
+ list_init( &fwds );
+
+ session->state = LHLO;
+ return 1;
+ }
+ case LMTP_HELP :
+ {
+ int helpcmd;
+
+ if (value != NULL)
+ for (helpcmd = LMTP_STRT; helpcmd < LMTP_END; helpcmd++)
+ if (strcasecmp(value, commands[helpcmd]) == 0) break;
+
+ trace(TRACE_DEBUG,"lmtp(): LMTP_HELP requested for commandtype %d", helpcmd);
+
+ if( (helpcmd==LMTP_LHLO) || (helpcmd==LMTP_DATA) ||
+ (helpcmd==LMTP_RSET) || (helpcmd==LMTP_QUIT) ||
+ (helpcmd==LMTP_NOOP) || (helpcmd==LMTP_HELP) )
+ {
+ fprintf((FILE *)stream, LMTP_HELP_TEXT[helpcmd]);
+ }
+ else
+ {
+ fprintf((FILE *)stream, LMTP_HELP_TEXT[LMTP_STRT]);
+ }
+
+ return 1;
+ }
+ case LMTP_VRFY :
+ {
+ /* RFC 2821 says this SHOULD be implemented...
+ * and the goal is to say if the given address
+ * is a valid delivery address at this server. */
+ fprintf((FILE *)stream, "502 Command not implemented\r\n" );
+ return 1;
+ }
+ case LMTP_EXPN:
+ {
+ /* RFC 2821 says this SHOULD be implemented...
+ * and the goal is to return the membership
+ * of the specified mailing list. */
+ fprintf((FILE *)stream, "502 Command not implemented\r\n" );
+ return 1;
+ }
+ case LMTP_MAIL:
+ {
+ /* We need to LHLO first because the client
+ * needs to know what extensions we support.
+ * */
+ if (session->state != LHLO)
+ {
+ fprintf((FILE *)stream, "550 Command out of sequence.\r\n");
+ }
+ else if (envelopefrom != NULL)
+ {
+ fprintf((FILE *)stream, "500 Sender already received. Use RSET to clear.\r\n");
+ }
+ else
+ {
+ /* First look for an email address.
+ * Don't bother verifying or whatever,
+ * just find something between angle brackets!
+ * */
+ int goodtogo=1;
+ size_t tmplen=0;
+ char *tmpleft=NULL, *tmpright=NULL, *tmpbody=NULL;
+
+ tmpleft = value;
+ tmpright = value + strlen(value);
+
+ /* eew pointer math... inspired by injector.c */
+
+ while (tmpleft[0] != '<' && tmpleft < tmpright )
+ tmpleft++;
+ while (tmpright[0] != '>' && tmpright > tmpleft )
+ tmpright--;
+
+ /* Step left up to skip '<' left angle bracket */
+ tmpleft++;
+
+ tmplen = tmpright - tmpleft;
+
+ /* Second look for a BODY keyword.
+ * See if it has an argument, and if we
+ * support that feature. Don't give an OK
+ * if we can't handle it yet, like 8BIT!
+ * */
+
+ /* Find the '=' following the address
+ * then advance one character past it
+ * (but only if there's more string!)
+ * */
+ tmpbody = strstr(tmpright, "=");
+ if (tmpbody != NULL)
+ if (strlen(tmpbody))
+ tmpbody++;
+
+ /* This is all a bit nested now... */
+ if (tmplen < 1)
+ {
+ fprintf((FILE *)stream,"500 No address found.\r\n" );
+ }
+ else if (tmpbody != NULL)
+ {
+ /* See RFC 3030 for the best
+ * description of this stuff.
+ * */
+ if (strlen(tmpbody) < 4)
+ {
+ /* Caught */
+ }
+ else if (0 == strcasecmp(tmpbody, "7BIT"))
+ {
+ /* Sure fine go ahead. */
+ goodtogo = 1; // Not that it wasn't 1 already ;-)
+ }
+ /* 8BITMIME corresponds to RFC 1652,
+ * BINARYMIME corresponds to RFC 3030.
+ * */
+ else if (strlen(tmpbody) < 8)
+ {
+ /* Caught */
+ }
+ else if (0 == strcasecmp(tmpbody, "8BITMIME"))
+ {
+ /* We can't do this yet. */
+ /* session->state = BIT8;
+ * */
+ fprintf((FILE *)stream,"500 Please use 7BIT MIME only.\r\n");
+ goodtogo = 0;
+ }
+ else if (strlen(tmpbody) < 10)
+ {
+ /* Caught */
+ }
+ else if (0 == strcasecmp(tmpbody, "BINARYMIME"))
+ {
+ /* We can't do this yet. */
+ /* session->state = BDAT;
+ * */
+ fprintf((FILE *)stream,"500 Please use 7BIT MIME only.\r\n" );
+ goodtogo = 0;
+ }
+ }
+
+ if (goodtogo)
+ {
+ /* Sure fine go ahead. */
+ memtst((envelopefrom=(char *)my_malloc(tmplen+1))==NULL);
+ memset(envelopefrom,0,tmplen+1);
+ strncpy(envelopefrom,tmpleft,tmplen);
+ // envelopefrom[tmplen+1] = '\0';
+ fprintf((FILE *)stream,"250 Sender <%s> OK\r\n", envelopefrom );
+ }
+ }
+ return 1;
+ }
+ case LMTP_RCPT :
+ {
+ /* This would be the non-piplined version...
+ else if (0 < auth_check_user_ext(value, userids, fwds, -1))
+ {
+ fprintf((FILE *)stream, "250 OK\r\n" );
+ }
+ else
+ {
+ fprintf((FILE *)stream, "550 No such user here\r\n" );
+ }
+ return 1;
+ */
+
+ if (session->state != LHLO)
+ {
+ fprintf((FILE *)stream, "550 Command out of sequence.\r\n");
+ }
+ else
+ {
+ size_t tmplen;
+ char *tmpleft, *tmpright, *tmprcpt;
+
+ tmpleft = value;
+ tmpright = value + strlen(value);
+
+ /* eew pointer math... inspired by injector.c */
+
+ while (tmpleft[0] != '<' && tmpleft < tmpright )
+ tmpleft++;
+ while (tmpright[0] != '>' && tmpright > tmpleft )
+ tmpright--;
+
+ /* Step left up to skip '<' left angle bracket */
+ tmpleft++;
+
+ tmplen = tmpright - tmpleft;
+
+ if (tmplen < 1)
+ {
+ fprintf((FILE *)stream,"500 No address found.\r\n" );
+ }
+ else
+ {
+ /* Note that list_nodeadd cannot NULL terminate
+ * because it does not know what kind of data it gets! */
+ memtst((tmprcpt=(char *)my_malloc(tmplen+1))==NULL);
+ memset(tmprcpt,0,tmplen+1);
+ strncpy(tmprcpt,tmpleft,tmplen);
+ // tmprcpt[tmplen+1] = 0;
+
+ /* Just add it to the list, and process at DATA time */
+ /* Make sure to pass the terminator at +1 */
+ list_nodeadd(&rcpt, tmprcpt, tmplen+1);
+
+ /* Is there a way to know if the client wants
+ * to pipeline or not? The RFC for LMTP implies that
+ * they will pipeline, because pipelining is mandatory
+ * for LMTP servers... but... I dunno? What if they're waiting?
+ */
+ // fprintf((FILE *)stream,"250 Recipient <%s> OK\r\n", tmprcpt );
+
+ my_free(tmprcpt);
+ }
+ }
+ return 1;
+ }
+ /* Here's where it gets really exciting! */
+ case LMTP_DATA:
+ {
+ // if (session->state != DATA || session->state != BIT8)
+ if (session->state != LHLO)
+ {
+ fprintf((FILE *)stream, "550 Command out of sequence\r\n" );
+ }
+ else if (list_totalnodes(&rcpt) < 1)
+ {
+ fprintf((FILE *)stream, "554 No valid recipients\r\n" );
+ }
+ else
+ {
+ struct element *tmpnode;
+
+ /* The replies MUST be in the order received */
+ rcpt.start = list_reverse(rcpt.start);
+
+ tmpnode = list_getstart(&rcpt);
+ while( tmpnode != NULL )
+ {
+ if (auth_check_user_ext(tmpnode->data, &userids, &fwds, -1) > 0 )
+ {
+ fprintf((FILE *)stream, "250 Recipient <%s> OK\r\n", (char *)tmpnode->data );
+ }
+ else
+ {
+ fprintf((FILE *)stream, "550 Recipient <%s> FAIL\r\n", (char *)tmpnode->data );
+ }
+ tmpnode = tmpnode->nextnode;
+ }
+
+ /* Now we have a list of recipients! */
+ /* Let the client know if they should continue... */
+
+ if (list_totalnodes(&userids) > 0 || list_totalnodes(&fwds) > 0)
+ {
+ fprintf((FILE *)stream, "354 Start mail input; end with <CRLF>.<CRLF>\r\n" );
+ }
+ else
+ {
+ fprintf((FILE *)stream, "554 No valid recipients.\r\n" );
+ return 1;
+ }
+
+ /* If we returned due to no recipients, and the remote
+ * system starts sending a message... well... they'll
+ * get disconnected pretty quickly from max_errors.
+ * */
+ {
+ char *header = NULL;
+ u64_t headersize=0, newlines=0;
+ u64_t dummyidx=0,dummysize=0;
+ struct list fromlist, headerfields, errusers;
+ struct element *tmpnode_rcpt;
+ struct element *tmpnode_errs;
+
+ list_init(&errusers);
+ list_init(&mimelist);
+ list_init(&fromlist);
+ list_init(&headerfields);
+
+ if (envelopefrom != NULL)
+ list_nodeadd(&fromlist, envelopefrom, strlen(envelopefrom));
+ else
+ {
+ trace(TRACE_DEBUG,"main(): envelopefrom is empty so no go");
+ fprintf((FILE *)stream, "554 No valid sender.\r\n" );
+ return 1;
+ }
+
+ if (!read_header((FILE *)instream, &newlines, &headersize, &header))
+ {
+ trace(TRACE_ERROR,"main(): fatal error from read_header()");
+ fprintf((FILE *)stream, "500 Error reading header.\r\n" );
+ return 1;
+ }
+
+ trace(TRACE_ERROR,"main(): lines of read_header() header is [%d]", newlines);
+
+ if (header != NULL)
+ {
+ trace(TRACE_ERROR,"main(): size of read_header() header is [%d]", headersize);
+ }
+ else
+ {
+ trace(TRACE_ERROR,"main(): read_header() returned a null header [%s]", header);
+ fprintf((FILE *)stream, "500 Error reading header.\r\n" );
+ return 1;
+ }
+
+ /* Parse the list and scan for field and content */
+ if (mime_readheader(header, &dummyidx, &mimelist, &dummysize) < 0)
+ {
+ trace(TRACE_ERROR,"main(): fatal error from mime_readheader()");
+ return 1;
+ }
+
+ /* FIXME: A negative return code from insert_messages() means
+ * that delivery died halfway through. There may be much more
+ * data coming from the client that we need to discard after
+ * giving back an unsuccessful return code. */
+ insert_messages((FILE *)instream, header, headersize,
+ &rcpt, &errusers,
+ &fromlist, 0,
+ NULL, &headerfields);
+ if (header != NULL)
+ my_free(header);
+
+ /* Use the 250 code if 1 or more deliveries were successful,
+ * and report the errors individually later
+ * Use the 503 code is 0 deliveries went through.
+ *
+ * This is a weird little assumption... that if there
+ * was some problem assembling the list of errors,
+ * assume everyone failed. Basically it's all we
+ * can do since otherwise we can't know who did or
+ * did not fail anyways!
+ * */
+ if (rcpt.total_nodes != errusers.total_nodes)
+ {
+ fprintf((FILE *)stream, "503 Message not received %ld FAIL\r\n", errusers.total_nodes );
+
+ tmpnode_rcpt = list_getstart(&rcpt);
+ while (tmpnode_rcpt != NULL )
+ {
+ fprintf((FILE *)stream, "450 Recipient <%s> FAIL\r\n", (char *)tmpnode_rcpt->data );
+ tmpnode_rcpt = tmpnode_rcpt->nextnode;
+ }
+ }
+ else
+ {
+ fprintf((FILE *)stream, "250 Message received OK\r\n" );
+
+ /* The replies MUST be in the order received */
+ rcpt.start = list_reverse(rcpt.start);
+
+ /* The errors MUST be in the same order as rcpt */
+ errusers.start = list_reverse(errusers.start);
+
+ tmpnode_rcpt = list_getstart(&rcpt);
+ tmpnode_errs = list_getstart(&errusers);
+ while (tmpnode_rcpt != NULL && tmpnode_errs != NULL)
+ {
+ /* These are evil magic numbers
+ * which must match pipe.c
+ * FIXME: define these!
+ * */
+ switch ((int)tmpnode_errs->data)
+ {
+ case 1:
+ fprintf((FILE *)stream, "250 Recipient <%s> OK\r\n", (char *)tmpnode_rcpt->data );
+ break;
+ case 0:
+ fprintf((FILE *)stream, "450 Recipient <%s> FAIL\r\n", (char *)tmpnode_rcpt->data );
+ break;
+ default:
+ fprintf((FILE *)stream, "450 Recipient <%s> FAIL\r\n", (char *)tmpnode_rcpt->data );
+ break;
+ }
+ tmpnode_rcpt = tmpnode_rcpt->nextnode;
+ tmpnode_errs = tmpnode_errs->nextnode;
+ }
+ }
+ }
+ }
+ return 1;
+ }
+ default :
+ {
+ return lmtp_error(session, stream,"500 What are you trying to say here?\r\n");
+ }
+ }
+ return 1;
+}
+