]> code.citadel.org Git - citadel.git/blobdiff - citadel/netproc.c
* Issue 'cancel' messages for vCard when a user is deleted.
[citadel.git] / citadel / netproc.c
index a61f51a2446cda26207311f779688520a5b22a1e..5f6fc197b037130aa43f3c7dc44ec052e8f8ae2f 100644 (file)
@@ -7,14 +7,20 @@
 /* How long it takes for an old node to drop off the network map */
 #define EXPIRY_TIME    (2592000L)
 
+/* How long we keep recently arrived messages in the use table */
+#define USE_TIME       (604800L)
+
 /* Where do we keep our lock file? */
 #define LOCKFILE       "/var/lock/LCK.netproc"
 
 /* Path to the 'uudecode' utility (needed for network file transfers) */
 #define UUDECODE       "/usr/bin/uudecode"
 
+/* Files used by the networker */
+#define MAILSYSINFO    "./network/mail.sysinfo"
+
 /* Uncomment the DEBUG def to see noisy traces */
-/* #define DEBUG 1 */
+#define DEBUG 1
 
 
 #include "sysdep.h"
@@ -30,7 +36,9 @@
 #include <signal.h>
 #include <errno.h>
 #include <syslog.h>
+#ifdef HAVE_GDBM_H
 #include <gdbm.h>
+#endif
 #include "citadel.h"
 #include "tools.h"
 
@@ -60,6 +68,7 @@ struct filterlist {
        struct filterlist *next;
        char f_person[64];
        char f_room[64];
+       char f_system[64];
 };
 
 struct syslist {
@@ -81,11 +90,14 @@ void get_config(void);
 
 struct filterlist *filter = NULL;
 struct syslist *slist = NULL;
+struct msglist *purgelist = NULL;
 
 struct config config;
 extern char bbs_home_directory[];
 extern int home_specified;
 
+GDBM_FILE use_table;
+
 
 #ifndef HAVE_STRERROR
 /*
@@ -109,7 +121,7 @@ void strip_trailing_whitespace(char *buf)
 
 
 /*
- * we also load the network/mail.sysinfo table into memory, make changes
+ * we also load the mail.sysinfo table into memory, make changes
  * as we learn more about the network from incoming messages, and write
  * the table back to disk when we're done.
  */
@@ -120,7 +132,7 @@ int load_syslist(void)
        char insys = 0;
        char buf[128];
 
-       fp = fopen("network/mail.sysinfo", "r");
+       fp = fopen(MAILSYSINFO, "r");
        if (fp == NULL)
                return (1);
 
@@ -225,7 +237,7 @@ void rewrite_syslist(void)
        time_t now;
 
        time(&now);
-       newfp = fopen("network/mail.sysinfo", "w");
+       newfp = fopen(MAILSYSINFO, "w");
        for (stemp = slist; stemp != NULL; stemp = stemp->next) {
                if (!strcasecmp(stemp->s_name, config.c_nodename)) {
                        time(&stemp->s_lastcontact);
@@ -301,6 +313,11 @@ int set_lockfile(void)
                        return 1;
        }
        lfp = fopen(LOCKFILE, "w");
+       if (lfp == NULL) {
+               syslog(LOG_NOTICE, "Cannot create %s: %s", LOCKFILE,
+                       strerror(errno));
+               return(1);
+       }
        fprintf(lfp, "%ld\n", (long) getpid());
        fclose(lfp);
        return (0);
@@ -409,10 +426,13 @@ int is_banned(char *k_person, char *k_room, char *k_system)
        return (0);
 }
 
-int get_sysinfo_type(char *name)
-{                              /* determine routing from sysinfo file */
+/*
+ * Determine routing from sysinfo file
+ */
+int get_sysinfo_type(char *name) {
        struct syslist *stemp;
-      GETSN:for (stemp = slist; stemp != NULL; stemp = stemp->next) {
+
+GETSN: for (stemp = slist; stemp != NULL; stemp = stemp->next) {
                if (!strcasecmp(stemp->s_name, name)) {
                        if (!strcasecmp(stemp->s_type, "use")) {
                                strcpy(name, stemp->s_nexthop);
@@ -454,35 +474,24 @@ void fpgetfield(FILE * fp, char *string)
  * Load all of the fields of a message, except the actual text, into a
  * table in memory (so we know how to process the message).
  */
-void msgfind(char *msgfile, struct minfo *buffer)
+void fpmsgfind(FILE *fp, struct minfo *buffer)
 {
        int b, e, mtype, aflag;
        char bbb[1024];
        char userid[1024];
-       FILE *fp;
 
        strcpy(userid, "");
-       fp = fopen(msgfile, "rb");
-       if (fp == NULL) {
-               syslog(LOG_ERR, "can't open message file: %s", strerror(errno));
-               return;
-       }
        e = getc(fp);
        if (e != 255) {
-               syslog(LOG_ERR, "incorrect message format");
+               syslog(LOG_ERR, "Magic number check failed for this message");
                goto END;
        }
+
+       memset(buffer, 0, sizeof(struct minfo));
        mtype = getc(fp);
        aflag = getc(fp);
-       buffer->I = 0L;
-       buffer->R[0] = 0;
-       buffer->E[0] = 0;
-       buffer->H[0] = 0;
-       buffer->S[0] = 0;
-       buffer->B[0] = 0;
-       buffer->G[0] = 0;
-
-      BONFGM:b = getc(fp);
+
+BONFGM:        b = getc(fp);
        if (b < 0)
                goto END;
        if (b == 'M')
@@ -539,13 +548,44 @@ void msgfind(char *msgfile, struct minfo *buffer)
                strcpy(buffer->G, bbb);
        if (b == 'E')
                strcpy(buffer->E, bbb);
+       if (b == 'Z')
+               strcpy(buffer->Z, bbb);
        goto BONFGM;
 
-      END:if (buffer->I == 0L)
+END:
+
+       /* NOTE: we used to use the following two lines of code to assign
+        * the timestamp as a message-ID if there was no message-ID already
+        * in the message.  We don't do this anymore because it screws up
+        * the loopzapper.
+        *
+       if (buffer->I == 0L)
                buffer->I = buffer->T;
+        */
+}
+
+
+/*
+ * msgfind() is the same as fpmsgfind() except it accepts a filename
+ * instead of a file handle.
+ */
+void msgfind(char *msgfile, struct minfo *buffer) {
+       FILE *fp;
+
+       fp = fopen(msgfile, "rb");
+       if (fp == NULL) {
+               syslog(LOG_ERR, "can't open %s: %s", msgfile, strerror(errno));
+               return;
+       }
+
+       fpmsgfind(fp, buffer);
        fclose(fp);
 }
 
+
+
+
+
 void ship_to(char *filenm, char *sysnm)
 {                              /* send spool file filenm to system sysnm */
        char sysflnm[100];
@@ -680,20 +720,99 @@ void bounce(struct minfo *bminfo)
 
 
 
+/*
+ * Generate a Message-ID string for the use table
+ */
+void strmsgid(char *buf, struct minfo *msginfo) {
+       int i;
+
+       sprintf(buf, "%ld@%s", msginfo->I, msginfo->N);
+       for (i=0; i<strlen(buf); ++i) {
+               if (isspace(buf[i])) {
+                       strcpy(&buf[i], &buf[i+1]);
+               }
+               buf[i] = tolower(buf[i]);
+       }
+}
+
+
 
 /*
- * Purge any old entries out of the use table
+ * Check the use table to see if a message has been here before.
+ * Returns 1 if the message is a duplicate; otherwise, it returns
+ * 0 and the message ID is added to the use table.
  */
-void purge_use_table(GDBM_FILE ut) {
+int already_received(GDBM_FILE ut, struct minfo *msginfo) {
+       char buf[256];
+       time_t now;
+       datum mkey, newrec;
+       int retval = 0;
+
+       /* We can't check for dups on a zero msgid, so just pass them through */
+       if ((msginfo->I)==0L) {
+               return 0;
+       }
+
+       strmsgid(buf, msginfo);
+       now = time(NULL);
+
+       mkey.dptr = buf;
+       mkey.dsize = strlen(buf);
+
+       /* Set return value to 1 if message exists */
+       if (gdbm_exists(ut, mkey)) {
+               retval = 1;
+       }
+
+       /* Write a record into the use table for this message.
+        * Replace existing records; this keeps the timestamp fresh.
+        */
+       newrec.dptr = (char *)&now;
+       newrec.dsize = sizeof(now);
+       gdbm_store(ut, mkey, newrec, GDBM_REPLACE);
+
+       return(retval);
 }
 
-/* FIX ... this isn't even close to being finished yet.
- * Here's what needs to be done:
- * 1. Write an entry for each received message into the table
- * 2. Check incoming messages against the table
- * 3. Post a list of rejected messages
- * 4. Purge entries more than a few days old
+
+
+/*
+ * Purge any old entries out of the use table.
+ * 
+ * Yes, you're reading this correctly: it keeps traversing the table until
+ * it manages to do a complete pass without deleting any records.  Read the
+ * gdbm man page to find out why.
+ *
  */
+void purge_use_table(GDBM_FILE ut) {
+       datum mkey, nextkey, therec;
+       int purged_anything = 0;
+       time_t rec_timestamp, now;
+
+       now = time(NULL);
+
+       do {
+               purged_anything = 0;
+               mkey = gdbm_firstkey(ut);
+               while (mkey.dptr != NULL) {
+                       therec = gdbm_fetch(ut, mkey);
+                       if (therec.dptr != NULL) {
+                               memcpy(&rec_timestamp, therec.dptr,
+                                       sizeof(time_t));
+                               free(therec.dptr);
+
+                               if ((now - rec_timestamp) > USE_TIME) {
+                                       gdbm_delete(ut, mkey);
+                                       purged_anything = 1;
+                               }
+
+                       }
+                       nextkey = gdbm_nextkey(ut, mkey);
+                       free(mkey.dptr);
+                       mkey = nextkey;
+               }
+       } while (purged_anything != 0);
+}
 
 
 
@@ -704,7 +823,7 @@ void purge_use_table(GDBM_FILE ut) {
  */
 void inprocess(void)
 {
-       FILE *fp, *message, *testfp, *ls;
+       FILE *fp, *message, *testfp, *ls, *duplist;
        static struct minfo minfo;
        struct recentmsg recentmsg;
        char tname[128], aaa[1024], iname[256], sfilename[256], pfilename[256];
@@ -715,7 +834,7 @@ void inprocess(void)
        char buf[256];
        long msglen;
        int bloklen;
-       GDBM_FILE use_table;
+       int valid_msg;
 
        /* temp file names */
        sprintf(tname, tmpnam(NULL));
@@ -726,13 +845,9 @@ void inprocess(void)
        /* Make sure we're in the right directory */
        chdir(bbs_home_directory);
 
-       /* Open the use table */
-       use_table = gdbm_open("./data/usetable.gdbm", 512,
-                             GDBM_WRCREAT, 0600, 0);
-       if (use_table == NULL) {
-               syslog(LOG_ERR, "could not open use table: %s",
-                      strerror(errno));
-       }
+       /* temporary file to contain a log of rejected dups */
+       duplist = tmpfile();
+
        /* Let the shell do the dirty work. Get all data from spoolin */
        do {
                sprintf(aaa, "cd %s/network/spoolin; ls", bbs_home_directory);
@@ -744,6 +859,10 @@ void inprocess(void)
                        do {
 SKIP:                          ptr = fgets(sfilename, sizeof sfilename, ls);
                                if (ptr != NULL) {
+#ifdef DEBUG
+                                       syslog(LOG_DEBUG,
+                                               "Trying %s", sfilename);
+#endif
                                        sfilename[strlen(sfilename) - 1] = 0;
                                        if (!strcmp(sfilename, ".")) goto SKIP;
                                        if (!strcmp(sfilename, "..")) goto SKIP;
@@ -770,9 +889,11 @@ NXMSG:     /* Seek to the beginning of the next message */
                                goto ENDSTR;
 
                        /* This crates the temporary file. */
+                       valid_msg = 1;
                        message = fopen(tname, "wb");
                        if (message == NULL) {
-                               syslog(LOG_ERR, "error creating %s: %s", tname, strerror(errno));
+                               syslog(LOG_ERR, "error creating %s: %s",
+                                       tname, strerror(errno));
                                goto ENDSTR;
                        }
                        putc(255, message);     /* 0xFF (start-of-message) */
@@ -781,24 +902,34 @@ NXMSG:    /* Seek to the beginning of the next message */
                        a = getc(fp);
                        putc(a, message);       /* mode */
                        do {
-                               FieldID = getc(fp);     /* Header field ID */
-                               putc(FieldID, message);
-                               do {
-                                       a = getc(fp);
-                                       putc(a, message);
-                               } while (a > 0);
+                               FieldID = getc(fp); /* Header field ID */
+                               if (isalpha(FieldID)) {
+                                       putc(FieldID, message);
+                                       do {
+                                               a = getc(fp);
+                                               if (a < 127) putc(a, message);
+                                       } while (a > 0);
+                                       if (a != 0) putc(0, message);
+                               }
+                               else {  /* Invalid field ID; flush it */
+                                       do {
+                                               a = getc(fp);
+                                       } while (a > 0);
+                                       valid_msg = 0;
+                               }
                        } while ((FieldID != 'M') && (a >= 0));
                        /* M is always last */
+                       if (FieldID != 'M') valid_msg = 0;
 
                        msglen = ftell(message);
                        fclose(message);
 
+                       if (!valid_msg) {
+                               unlink(tname);
+                               goto NXMSG;
+                       }
+
                        /* process the individual mesage */
-                       minfo.D[0] = 0;
-                       minfo.C[0] = 0;
-                       minfo.B[0] = 0;
-                       minfo.G[0] = 0;
-                       minfo.R[0] = 0;
                        msgfind(tname, &minfo);
                        strncpy(recentmsg.RMnodename, minfo.N, 9);
                        recentmsg.RMnodename[9] = 0;
@@ -856,8 +987,17 @@ NXMSG:     /* Seek to the beginning of the next message */
                                if (strlen(minfo.G) > 0)
                                        strcpy(stemp->s_gdom, minfo.G);
                        }
+
+                       /* Check the use table; reject message if it's been here before */
+                       if (already_received(use_table, &minfo)) {
+                               syslog(LOG_NOTICE, "rejected duplicate message");
+                               fprintf(duplist, "#%ld fm <%s> in <%s> @ <%s>\n",
+                                       minfo.I, minfo.A, minfo.O, minfo.N);
+                       }
+
+
                        /* route the message if necessary */
-                       if ((strcasecmp(minfo.D, NODENAME)) && (minfo.D[0] != 0)) {
+                       else if ((strcasecmp(minfo.D, NODENAME)) && (minfo.D[0] != 0)) {
                                a = get_sysinfo_type(minfo.D);
                                syslog(LOG_NOTICE, "routing message to system <%s>", minfo.D);
                                fflush(stdout);
@@ -878,13 +1018,14 @@ NXMSG:   /* Seek to the beginning of the next message */
                                        /* message falls into the bit bucket? */
                                }
                        }
+
                        /* check to see if it's a file transfer */
                        else if (!strncasecmp(minfo.S, "FILE", 4)) {
                                proc_file_transfer(tname);
                        }
+
                        /* otherwise process it as a normal message */
                        else {
-
                                if (!strcasecmp(minfo.R, "postmaster")) {
                                        strcpy(minfo.R, "");
                                        strcpy(minfo.C, "Aide");
@@ -946,19 +1087,43 @@ NXMSG:   /* Seek to the beginning of the next message */
                                }
 
                                fclose(message);
+                               
                        }
 
                        unlink(tname);
                        goto NXMSG;
 
-                     ENDSTR:fclose(fp);
+ENDSTR:                        fclose(fp);
                        unlink(pfilename);
                }
        } while (ptr != NULL);
        unlink(iname);
 
-       purge_use_table(use_table);
-       gdbm_close(use_table);
+
+       /*
+        * If dups were rejected, post a message saying so
+        */
+       if (ftell(duplist)!=0L) {
+               fp = fopen("./network/spoolin/ctdl_rejects", "ab");
+               if (fp != NULL) {
+                       fprintf(fp, "%cA%c", 255, 1);
+                       fprintf(fp, "T%ld%c", time(NULL), 0);
+                       fprintf(fp, "ACitadel%c", 0);
+                       fprintf(fp, "OAide%cM", 0);
+                       fprintf(fp, "The following duplicate messages"
+                               " were rejected:\n \n");
+                       rewind(duplist);
+                       while (fgets(buf, sizeof(buf), duplist) != NULL) {
+                               buf[strlen(buf)-1] = 0;
+                               fprintf(fp, " %s\n", buf);
+                       }
+                       fprintf(fp, "%c", 0);
+                       pclose(fp);
+               }
+       }
+
+       fclose(duplist);
+
 }
 
 
@@ -983,15 +1148,17 @@ int checkpath(char *path, char *sys)
 }
 
 /*
- * implement split horizon algorithm
+ * Implement split horizon algorithm (prevent infinite spooling loops
+ * by refusing to send any node a message which already contains its
+ * nodename in the path).
  */
-int ismsgok(long int mpos, FILE * mmfp, char *sysname)
+int ismsgok(FILE *mmfp, char *sysname)
 {
        int a;
        int ok = 0;             /* fail safe - no path, don't send it */
        char fbuf[256];
 
-       fseek(mmfp, mpos, 0);
+       fseek(mmfp, 0L, 0);
        if (getc(mmfp) != 255)
                return (0);
        getc(mmfp);
@@ -1011,6 +1178,54 @@ int ismsgok(long int mpos, FILE * mmfp, char *sysname)
 
 
 
+
+/*
+ * Add a message to the list of messages to be deleted off the local server
+ * at the end of this run.
+ */
+void delete_locally(long msgid, char *roomname) {
+       struct msglist *mptr;
+
+       mptr = (struct msglist *) malloc(sizeof(struct msglist));
+       mptr->next = purgelist;
+       mptr->m_num = msgid;
+       strcpy(mptr->m_rmname, roomname);
+       purgelist = mptr;
+}
+
+
+
+/*
+ * Delete all messages on the purge list from the local server.
+ */
+void process_purgelist(void) {
+       char curr_rm[ROOMNAMELEN];
+       char buf[256];
+       struct msglist *mptr;
+
+
+       strcpy(curr_rm, "__nothing__");
+       while (purgelist != NULL) {
+               if (strcasecmp(curr_rm, purgelist->m_rmname)) {
+                       sprintf(buf, "GOTO %s", purgelist->m_rmname);
+                       serv_puts(buf);
+                       serv_gets(buf);
+                       if (buf[0] == '2') extract(curr_rm, &buf[4], 0);
+               }
+               if (strcasecmp(curr_rm, purgelist->m_rmname)) {
+                       sprintf(buf, "DELE %ld", purgelist->m_num);
+                       serv_puts(buf);
+                       serv_gets(buf);
+               }
+               mptr = purgelist->next;
+               free(purgelist);
+               purgelist = mptr;
+       }
+}
+
+
+
+
 /* spool list of messages to a file */
 /* returns # of msgs spooled */
 int spool_out(struct msglist *cmlist, FILE * destfp, char *sysname)
@@ -1022,6 +1237,7 @@ int spool_out(struct msglist *cmlist, FILE * destfp, char *sysname)
        int msgs_spooled = 0;
        long msg_len;
        int blok_len;
+       static struct minfo minfo;
 
        char buf[256];
        char curr_rm[256];
@@ -1061,7 +1277,7 @@ int spool_out(struct msglist *cmlist, FILE * destfp, char *sysname)
 
                rewind(mmfp);
 
-               if (ismsgok(0L, mmfp, sysname)) {
+               if (ismsgok(mmfp, sysname)) {
                        ++msgs_spooled;
                        fflush(stdout);
                        fseek(mmfp, 0L, 0);
@@ -1075,6 +1291,9 @@ int spool_out(struct msglist *cmlist, FILE * destfp, char *sysname)
                                        fprintf(destfp, "%s!", NODENAME);
                                if (a != 'C')
                                        fwrite(fbuf, strlen(fbuf) + 1, 1, destfp);
+                               if (a == 'S') if (!strcasecmp(fbuf, "CANCEL")) {
+                                       delete_locally(cmptr->m_num, cmptr->m_rmname);
+                               }
                        }
                        if (a == 'M') {
                                fprintf(destfp, "C%s%c",
@@ -1085,6 +1304,14 @@ int spool_out(struct msglist *cmlist, FILE * destfp, char *sysname)
                                        putc(a, destfp);
                                } while (a > 0);
                        }
+
+               /* Get this message into the use table, so we can reject it
+                * if a misconfigured remote system sends it back to us.
+                */
+               fseek(mmfp, 0L, 0);
+               fpmsgfind(mmfp, &minfo);
+               already_received(use_table, &minfo);
+
                }
                fclose(mmfp);
        }
@@ -1297,8 +1524,14 @@ int main(int argc, char **argv)
                }
        }
 
+#ifdef DEBUG
+       syslog(LOG_DEBUG, "Calling get_config()");
+#endif
        get_config();
 
+#ifdef DEBUG
+       syslog(LOG_DEBUG, "Creating lock file");
+#endif
        if (set_lockfile() != 0) {
                syslog(LOG_NOTICE, "lock file exists: already running");
                cleanup(1);
@@ -1317,8 +1550,21 @@ int main(int argc, char **argv)
                syslog(LOG_ERR, "cannot load sysinfo");
        setup_special_nodes();
 
-       inprocess();            /* first collect incoming stuff */
+       /* Open the use table */
+       use_table = gdbm_open("./data/usetable.gdbm", 512,
+                             GDBM_WRCREAT, 0600, 0);
+       if (use_table == NULL) {
+               syslog(LOG_ERR, "could not open use table: %s",
+                      strerror(errno));
+       }
+
+       /* first collect incoming stuff */
+       inprocess();
 
+       /* Now process outbound messages, but NOT if this is just a
+        * quick import-only run (i.e. the -i command-line option
+        * was specified)
+        */
        if (import_only != 1) {
                allfp = (FILE *) popen("cd ./network/systems; ls", "r");
                if (allfp != NULL) {
@@ -1331,7 +1577,17 @@ int main(int argc, char **argv)
                /* import again in case anything new was generated */
                inprocess();
        }
+
+       /* Update mail.sysinfo with new information we learned */
        rewrite_syslist();
+
+       /* Delete any messages which need to be purged locally */
+       process_purgelist();
+
+       /* Close the use table */
+       purge_use_table(use_table);
+       gdbm_close(use_table);
+
        syslog(LOG_NOTICE, "processing ended.");
        cleanup(0);
        return 0;