]> code.citadel.org Git - citadel.git/blobdiff - citadel/serv_smtp.c
* Reworked some of the data structures to handle multiple recipients
[citadel.git] / citadel / serv_smtp.c
index 5cfa5fa8301fc94f57644fcd4b9989c1679a7e48..34a96804776b1b7ca620112d186ef62fc6d9f05b 100644 (file)
@@ -1,4 +1,5 @@
 /* $Id$ */
+
 #include "sysdep.h"
 #include <stdlib.h>
 #include <unistd.h>
 #include "database.h"
 #include "msgbase.h"
 #include "tools.h"
+#include "internet_addressing.h"
+#include "genstamp.h"
+
 
-struct citsmtp {
+struct citsmtp {               /* Information about the current session */
        int command_state;
        struct usersupp vrfy_buffer;
        int vrfy_count;
        char vrfy_match[256];
+       char from[256];
+       int number_of_recipients;
+       int delivery_mode;
 };
 
-enum {
+
+enum {                         /* Command states for login authentication */
        smtp_command,
        smtp_user,
        smtp_password
 };
 
-#define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
+enum {                         /* Delivery modes */
+       smtp_deliver_local,
+       smtp_deliver_remote
+};
 
-long SYM_SMTP;
+#define SMTP           ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
+#define SMTP_RECP      ((char *)CtdlGetUserData(SYM_SMTP_RECP))
 
+long SYM_SMTP;
+long SYM_SMTP_RECP;
 
 /*
  * Here's where our SMTP session begins its happy day.
  */
 void smtp_greeting(void) {
 
-       strcpy(CC->cs_clientname, "Citadel SMTP");
+       strcpy(CC->cs_clientname, "SMTP session");
+       CC->internal_pgm = 1;
+       CC->cs_flags |= CS_STEALTH;
        CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
+       CtdlAllocUserData(SYM_SMTP_RECP, 256);
+       sprintf(SMTP_RECP, "%s", "");
 
        cprintf("220 Welcome to the Citadel/UX ESMTP server at %s\n",
                config.c_fqdn);
@@ -80,12 +98,16 @@ void smtp_hello(char *argbuf, int is_esmtp) {
  */
 void smtp_help(void) {
        cprintf("214-Here's the frequency, Kenneth:\n");
+       cprintf("214-    DATA\n");
        cprintf("214-    EHLO\n");
        cprintf("214-    EXPN\n");
        cprintf("214-    HELO\n");
        cprintf("214-    HELP\n");
+       cprintf("214-    MAIL\n");
        cprintf("214-    NOOP\n");
        cprintf("214-    QUIT\n");
+       cprintf("214-    RCPT\n");
+       cprintf("214-    RSET\n");
        cprintf("214-    VRFY\n");
        cprintf("214 I could tell you more, but then I'd have to kill you.\n");
 }
@@ -123,6 +145,8 @@ void smtp_get_pass(char *argbuf) {
        if (CtdlTryPassword(password) == pass_ok) {
                cprintf("235 Authentication successful.\n");
                lprintf(9, "SMTP auth login successful\n");
+               CC->internal_pgm = 0;
+               CC->cs_flags &= ~CS_STEALTH;
        }
        else {
                cprintf("500 Authentication failed.\n");
@@ -138,7 +162,7 @@ void smtp_auth(char *argbuf) {
        char buf[256];
 
        if (strncasecmp(argbuf, "login", 5) ) {
-               cprintf("500 We only support LOGIN authentication.\n");
+               cprintf("550 We only support LOGIN authentication.\n");
                return;
        }
 
@@ -154,26 +178,6 @@ void smtp_auth(char *argbuf) {
 }
 
 
-/*
- * Return 0 if a given string fuzzy-matches a Citadel user account
- *
- * FIX ... this needs to be updated to match any and all ways of addressing
- *         a user.  It may even be appropriate to move this out of SMTP and
- *         into the server core.
- */
-int fuzzy_match(struct usersupp *us, char *matchstring) {
-       int a;
-
-       for (a=0; a<strlen(us->fullname); ++a) {
-               if (!strncasecmp(&us->fullname[a],
-                  matchstring, strlen(matchstring))) {
-                       return 0;
-               }
-       }
-       return -1;
-}
-
-
 /*
  * Back end for smtp_vrfy() command
  */
@@ -254,6 +258,249 @@ void smtp_expn(char *argbuf) {
 }
 
 
+/*
+ * Implements the RSET (reset state) command.
+ * Currently this just zeroes out the state buffer.  If pointers to data
+ * allocated with mallok() are ever placed in the state buffer, we have to
+ * be sure to phree() them first!
+ */
+void smtp_rset(void) {
+       memset(SMTP, 0, sizeof(struct citsmtp));
+       if (CC->logged_in) logout(CC);
+       cprintf("250 Zap!\n");
+}
+
+
+
+/*
+ * Implements the "MAIL From:" command
+ */
+void smtp_mail(char *argbuf) {
+       char user[256];
+       char node[256];
+       int cvt;
+
+       if (strlen(SMTP->from) != 0) {
+               cprintf("503 Only one sender permitted\n");
+               return;
+       }
+
+       if (strncasecmp(argbuf, "From:", 5)) {
+               cprintf("501 Syntax error\n");
+               return;
+       }
+
+       strcpy(SMTP->from, &argbuf[5]);
+       striplt(SMTP->from);
+
+       if (strlen(SMTP->from) == 0) {
+               cprintf("501 Empty sender name is not permitted\n");
+               return;
+       }
+
+
+       /* If this SMTP connection is from a logged-in user, make sure that
+        * the user only sends email from his/her own address.
+        */
+       if (CC->logged_in) {
+               cvt = convert_internet_address(user, node, SMTP->from);
+               lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
+               if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
+                       cprintf("550 <%s> is not your address.\n", SMTP->from);
+                       strcpy(SMTP->from, "");
+                       return;
+               }
+       }
+
+       /* Otherwise, make sure outsiders aren't trying to forge mail from
+        * this system.
+        */
+       else {
+               cvt = convert_internet_address(user, node, SMTP->from);
+               lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
+               if (!strcasecmp(node, config.c_nodename)) { /* FIX use fcn */
+                       cprintf("550 You must log in to send mail from %s\n",
+                               config.c_fqdn);
+                       strcpy(SMTP->from, "");
+                       return;
+               }
+       }
+
+       cprintf("250 Sender ok.  Groovy.\n");
+}
+
+
+
+/*
+ * Implements the "RCPT To:" command
+ */
+void smtp_rcpt(char *argbuf) {
+       int cvt;
+       char user[256];
+       char node[256];
+       char recp[256];
+       int is_spam = 0;        /* FIX implement anti-spamming */
+
+       if (strlen(SMTP->from) == 0) {
+               cprintf("503 MAIL first, then RCPT.  Duh.\n");
+               return;
+       }
+
+       if (strncasecmp(argbuf, "To:", 3)) {
+               cprintf("501 Syntax error\n");
+               return;
+       }
+
+       strcpy(recp, &argbuf[3]);
+       striplt(recp);
+       alias(recp);
+
+       cvt = convert_internet_address(user, node, recp);
+       sprintf(recp, "%s@%s", user, node);
+
+
+       switch(cvt) {
+               case rfc822_address_locally_validated:
+                       cprintf("250 %s is a valid recipient.\n", user);
+                       ++SMTP->number_of_recipients;
+                       CtdlReallocUserData(SYM_SMTP_RECP,
+                               strlen(SMTP_RECP) + 1024 );
+                       strcat(SMTP_RECP, "local|");
+                       strcat(SMTP_RECP, user);
+                       strcat(SMTP_RECP, "|0\n");
+                       return;
+
+               case rfc822_no_such_user:
+                       cprintf("550 %s: no such user\n", recp);
+                       return;
+
+               case rfc822_address_invalid:
+                       if (is_spam) {
+                               cprintf("551 Away with thee, spammer!\n");
+                       }
+                       else {
+                               cprintf("250 Remote recipient %s ok\n", recp);
+                               ++SMTP->number_of_recipients;
+                               CtdlReallocUserData(SYM_SMTP_RECP,
+                                       strlen(SMTP_RECP) + 1024 );
+                               strcat(SMTP_RECP, "remote|");
+                               strcat(SMTP_RECP, recp);
+                               strcat(SMTP_RECP, "|0\n");
+                               return;
+                       }
+                       return;
+       }
+
+       cprintf("599 Unknown error\n");
+}
+
+
+
+
+
+/*
+ * Back end for smtp_data()  ... this does the actual delivery of the message
+ * Returns 0 on success, nonzero on failure
+ */
+int smtp_message_delivery(struct CtdlMessage *msg) {
+       char user[256];
+       char node[256];
+       char name[256];
+       int successful_saves = 0;
+       int failed_saves = 0;
+       long msgid = (-1L);
+
+       lprintf(9, "smtp_message_delivery() called\n");
+
+       /* Fill in 'from' fields with envelope information if missing */
+       process_rfc822_addr(SMTP->from, user, node, name);
+       if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
+       if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
+       if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
+
+       /* Save the message in the queue */
+       msgid = CtdlSaveMsg(msg,
+               "",
+               SMTP_SPOOLOUT_ROOM,
+               MES_LOCAL,
+               1);
+       ++successful_saves;
+
+               /* FIX go thru each local user and save to boxes
+                       then stuff remote users in queue list
+                       then delete from queue if num remote users is 0
+               */
+
+       return(failed_saves);
+}
+
+
+
+/*
+ * Implements the DATA command
+ */
+void smtp_data(void) {
+       char *body;
+       struct CtdlMessage *msg;
+       int retval;
+       char nowstamp[256];
+
+       if (strlen(SMTP->from) == 0) {
+               cprintf("503 Need MAIL command first.\n");
+               return;
+       }
+
+       if (SMTP->number_of_recipients < 1) {
+               cprintf("503 Need RCPT command first.\n");
+               return;
+       }
+
+       cprintf("354 Transmit message now; terminate with '.' by itself\n");
+       
+       generate_rfc822_datestamp(nowstamp, time(NULL));
+       body = mallok(4096);
+       if (body != NULL) sprintf(body,
+               "Received: from %s\n"
+               "       by %s;\n"
+               "       %s\n",
+                       "FIX.FIX.com",
+                       config.c_fqdn,
+                       nowstamp);
+       
+       body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
+       if (body == NULL) {
+               cprintf("550 Unable to save message text: internal error.\n");
+               return;
+       }
+
+       lprintf(9, "Converting message...\n");
+       msg = convert_internet_message(body);
+
+       /* If the user is locally authenticated, FORCE the From: header to
+        * show up as the real sender
+        */
+       if (CC->logged_in) {
+               if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
+               if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
+               if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
+               msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
+               msg->cm_fields['N'] = strdoop(config.c_nodename);
+               msg->cm_fields['H'] = strdoop(config.c_humannode);
+       }
+
+       retval = smtp_message_delivery(msg);
+       CtdlFreeMessage(msg);
+
+       if (!retval) {
+               cprintf("250 Message accepted for delivery.\n");
+       }
+       else {
+               cprintf("550 Internal error.\n");
+       }
+}
+
+
+
 
 /* 
  * Main command loop for SMTP sessions.
@@ -283,6 +530,10 @@ void smtp_command_loop(void) {
                smtp_auth(&cmdbuf[5]);
        }
 
+       else if (!strncasecmp(cmdbuf, "DATA", 4)) {
+               smtp_data();
+       }
+
        else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
                smtp_hello(&cmdbuf[5], 1);
        }
@@ -299,6 +550,10 @@ void smtp_command_loop(void) {
                smtp_help();
        }
 
+       else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
+               smtp_mail(&cmdbuf[5]);
+       }
+
        else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
                cprintf("250 This command successfully did nothing.\n");
        }
@@ -309,23 +564,33 @@ void smtp_command_loop(void) {
                return;
                }
 
+       else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
+               smtp_rcpt(&cmdbuf[5]);
+       }
+
+       else if (!strncasecmp(cmdbuf, "RSET", 4)) {
+               smtp_rset();
+       }
+
        else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
                smtp_vrfy(&cmdbuf[5]);
        }
 
        else {
-               cprintf("500 I'm afraid I can't do that, Dave.\n");
+               cprintf("502 I'm afraid I can't do that, Dave.\n");
        }
 
 }
 
 
-
 char *Dynamic_Module_Init(void)
 {
        SYM_SMTP = CtdlGetDynamicSymbol();
-       CtdlRegisterServiceHook(25,
+       SYM_SMTP_RECP = CtdlGetDynamicSymbol();
+       CtdlRegisterServiceHook(SMTP_PORT,
                                smtp_greeting,
                                smtp_command_loop);
+       create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
        return "$Id$";
 }
+