/***************************************************************************** * * $Id$ * Purpose ...............: Mail and file queue operations * ***************************************************************************** * Copyright (C) 1997-2004 * * Michiel Broek FIDO: 2:280/2802 * Beekmansbos 10 * 1971 BV IJmuiden * the Netherlands * * This file is part of MBSE BBS. * * This BBS is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * MBSE BBS is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MBSE BBS; see the file COPYING. If not, write to the Free * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *****************************************************************************/ #include "../config.h" #include "../lib/mbselib.h" #include "../lib/users.h" #include "../lib/mbsedb.h" #include "fsort.h" #include "dirsession.h" #include "queue.h" static char *dow[] = {(char *)"su", (char *)"mo", (char *)"tu", (char *)"we", (char *)"th", (char *)"fr", (char *)"sa"}; extern int do_quiet; extern int flushed; extern pid_t mypid; /* * Internal version of basename to make this better portable. */ char *Basename(char *str) { char *cp = strrchr(str, '/'); return cp ? cp+1 : str; } /* * Flush one queue directory of a node. If everything is successfull the * directory will become empty. */ void flush_dir(char *); void flush_dir(char *ndir) { struct dirent *de; DIR *dp; FILE *fp, *inf, *ouf; faddr noden, *bestaka; fidoaddr nodenr; int flavor, mode, Attach, fage, first, bread, rc, fdn; long fsize; char *p, *temp, *fname, *arcfile, *pktfile, *ext, maxnr, nr, oldnr, *buf; time_t Now; struct tm *ptm; struct stat sbuf; fd_list *fdl = NULL; temp = calloc(PATH_MAX, sizeof(char)); sprintf(temp, "%s/%s", CFG.out_queue, ndir); if (chdir(temp) == -1) { WriteError("$Error chdir to %s", temp); free(temp); return; } /* * Get the nodenumber from the filename */ noden.domain = NULL; noden.name = NULL; noden.zone = atoi(strtok(ndir, ".")); noden.net = atoi(strtok(NULL, ".")); noden.node = atoi(strtok(NULL, ".")); noden.point = atoi(strtok(NULL, "\0")); if (SearchFidonet(noden.zone)) noden.domain = xstrcpy(fidonet.domain); memset(&nodenr, 0, sizeof(nodenr)); nodenr.zone = noden.zone; nodenr.net = noden.net; nodenr.node = noden.node; nodenr.point = noden.point; sprintf(nodenr.domain, "%s", noden.domain); if (!SearchNode(nodenr)) { /* * Node not known, blank noderecord and fill in some details * so that we are able to send mail crash or immediate. */ Syslog('+', "Node %s not in setup, using default settings", aka2str(nodenr)); memset(&nodes, 0, sizeof(nodes)); nodes.Aka[0].zone = noden.zone; nodes.Aka[0].net = noden.net; nodes.Aka[0].node = noden.node; nodes.Aka[0].point = noden.point; } /* * If we route via another aka, change everything. */ if (nodes.RouteVia.zone) { p = xstrcpy(aka2str(nodenr)); Syslog('+', "Route to %s via %s", p, aka2str(nodes.RouteVia)); free(p); noden.zone = nodes.RouteVia.zone; noden.net = nodes.RouteVia.net; noden.node = nodes.RouteVia.node; noden.point = nodes.RouteVia.point; if (noden.domain) free(noden.domain); noden.domain = xstrcpy(nodes.RouteVia.domain); /* * Load routevia noderecord to get the correct flavor. * If there is no noderecord, reload the old one. */ if (!SearchNode(nodes.RouteVia)) SearchNode(nodenr); } /* * At this point we are ready to add everything to the real outbound. * Lock the node, if this fails because the node is busy we abort * and try to add at a later time. */ if (nodes.Session_out == S_DIR) { if (islocked(nodes.Dir_out_clock, nodes.Dir_out_chklck, nodes.Dir_out_waitclr, 'p')) { Syslog('+', "Mail and files stay in queue, will be added later"); if (noden.domain) free(noden.domain); free(temp); return; } else { if (! setlock(nodes.Dir_out_mlock, nodes.Dir_out_mklck, 'p')) { Syslog('+', "Mail and files stay in queue, will be added later"); if (noden.domain) free(noden.domain); free(temp); return; } } } else { if (nodelock(&noden, mypid)) { Syslog('+', "Mail and files stay in queue, will be added later"); if (noden.domain) free(noden.domain); free(temp); return; } } /* * Create arcmail filename for this node. */ Now = time(NULL); arcfile = calloc(PATH_MAX, sizeof(char)); if (nodes.Session_out == S_DIR) { ptm = localtime(&Now); ext = dow[ptm->tm_wday]; if (!nodes.ARCmailCompat && (nodes.Aka[0].zone != noden.zone)) { /* * Generate ARCfile name from the CRC of the ASCII string of the node address. */ sprintf(arcfile, "%s/%08lx.%s0", nodes.Dir_out_path, StringCRC32(ascfnode(&noden, 0x1f)), ext); } else { bestaka = bestaka_s(&noden); if (noden.point) { sprintf(arcfile, "%s/%04x%04x.%s0", nodes.Dir_out_path, ((bestaka->net) - (noden.net)) & 0xffff, ((bestaka->node) - (noden.node) + (noden.point)) & 0xffff, ext); } else if (bestaka->point) { /* * Inserted the next code for if we are a point, * I hope this is ARCmail 0.60 compliant. 21-May-1999 */ sprintf(arcfile, "%s/%04x%04x.%s0", nodes.Dir_out_path, ((bestaka->net) - (noden.net)) & 0xffff, ((bestaka->node) - (noden.node) - (bestaka->point)) & 0xffff, ext); } else { sprintf(arcfile, "%s/%04x%04x.%s0", nodes.Dir_out_path, ((bestaka->net) - (noden.net)) & 0xffff, ((bestaka->node) - (noden.node)) &0xffff, ext); } } } else { sprintf(arcfile, "%s", arcname(&noden, nodes.Aka[0].zone, nodes.ARCmailCompat)); } Syslog('P', "Arcmail file %s", arcfile); /* * If there is a mailpkt.qqq file, close it and rename it. */ pktfile = calloc(PATH_MAX, sizeof(char)); fname = calloc(PATH_MAX, sizeof(char)); sprintf(fname, "%s/mailpkt.qqq", temp); if (access(fname, W_OK) == 0) { sprintf(pktfile, "%s/%08lx.pkt", temp, sequencer()); if (rename(fname, pktfile)) { WriteError("$Can't rename %s to %s", fname, pktfile); } else { /* * Add zero word to end of .pkt file */ if ((fp = fopen(pktfile, "a+")) == NULL) { WriteError("$Can't open %s", pktfile); } putc('\0', fp); putc('\0', fp); fsync(fileno(fp)); fclose(fp); Syslog('P', "Renamed %s to %s", fname, pktfile); } } free(fname); /* * Now all mail pkts are ready to archive */ if ((dp = opendir(temp)) == NULL) { WriteError("$Can't open %s", temp); free(temp); free(arcfile); if (nodes.Session_out == S_DIR) remlock(nodes.Dir_out_mlock, nodes.Dir_out_mklck, 'p'); else nodeulock(&noden, mypid); if (noden.domain) free(noden.domain); return; } /* * Read all .pkt filenames, get the timestamp and add them * to the memory array for later sort on filedate. */ while ((de = readdir(dp))) if ((strlen(de->d_name) == 12) && (strncasecmp(de->d_name+8,".pkt",4) == 0)) { stat(de->d_name, &sbuf); Syslog('p', "Adding %s to filelist", de->d_name); fill_fdlist(&fdl, de->d_name, sbuf.st_mtime); } closedir(dp); sort_fdlist(&fdl); if (getarchiver((char *)"ZIP")) { flavor = 'f'; if (nodes.Crash) flavor = 'c'; if (nodes.Hold) flavor = 'h'; } else { WriteError("Archiver ZIP not found"); if (noden.domain) free(noden.domain); free(temp); free(arcfile); return; } first = TRUE; while ((fname = pull_fdlist(&fdl)) != NULL) { /* * Check the size of the existing archive if there is a size limit. * Change to new archive names if the existing is too large. * If the archive size is zero, it's an already sent archive, the * number will be bumped also. * If the archive is older then 6 days, the name is also bumped. * Do this until we find a new name or if the last digit is a '9' or 'z'. * Purge archives older then toss_days. */ nr = oldnr = '0'; if (nodes.ARCmailAlpha) maxnr = 'z'; else maxnr = '9'; Attach = FALSE; for (;;) { fsize = file_size(arcfile); fage = (int)((Now - file_time(arcfile)) / 86400); if (fsize == -1L) { Attach = TRUE; break; } if (fsize == 0L) { if ((fage > 6) && (nr < maxnr)) { /* * Remove truncated ARCmail files older then 6 days. */ unlink(arcfile); fsize = -1L; Attach = TRUE; break; } /* * Increase filename extension if there is a truncated file of today. */ if (nr < maxnr) { nr++; if (nr == ('9' +1)) nr = 'a'; arcfile[strlen(arcfile) -1] = nr; } else { Syslog('!', "Warning: archive filename extensions exhausted for today"); break; } } else if (CFG.maxarcsize && (fsize > (CFG.maxarcsize * 1024)) && (nr < maxnr)) { /* * Use a new ARCmail file if the last one is too big. */ nr++; if (nr == ('9' +1)) nr = 'a'; arcfile[strlen(arcfile) -1] = nr; } fsize = file_size(arcfile); fage = (int)((Now - file_time(arcfile)) / 86400); if ((fsize > 0L) && (fage > 6) && (nr < maxnr)) { /* * If there is ARCmail of a week old or older, add mail * to a new ARCmail bundle. */ nr++; if (nr == ('9' +1)) nr = 'a'; arcfile[strlen(arcfile) -1] = nr; } if (oldnr == nr) break; else oldnr = nr; } fsize = file_size(arcfile); /* * If arcfile names were exhausted then the file ending on a z could still * be in the outbound but truncated if it has been sent. Since we are * reusing that filename (not a good solution) we must erase it or the * archiver program will complain. */ if (fsize == 0L) { unlink(arcfile); Attach = TRUE; } if (first) { Syslog('+', "Pack ARCmail for %s via %s", aka2str(nodenr), ascfnode(&noden, 0x1f)); if (!do_quiet) { printf("\rAdding ARCmail for %s ", ascfnode(&noden, 0x1f)); fflush(stdout); } first = FALSE; flushed = TRUE; } if (execute_str(archiver.marc, arcfile, fname, (char *)"/dev/null", (char *)"/dev/null", (char *)"/dev/null") == 0) { unlink(fname); } else { WriteError("Create ARCmail failed, trying again after sync()"); sync(); sleep(1); if (execute_str(archiver.marc, arcfile, fname, (char *)"/dev/null", (char *)"/dev/null", (char *)"/dev/null") == 0) { unlink(fname); } else { WriteError("Can't add %s to ARCmail archive", fname); Attach = FALSE; } } /* * Change filemode so downlink has rights to the file. */ if (nodes.Session_out == S_DIR) chmod(arcfile, 0660); /* * Attach file to .flo, not for FTP or Directory sessions. */ if (Attach && nodes.Session_out == S_DIRECT) attach(noden, arcfile, TFS, flavor, FALSE); } /* * Open directory again. */ if ((dp = opendir(temp)) == NULL) { WriteError("$Can't open %s", temp); free(temp); free(arcfile); if (nodes.Session_out == S_DIR) remlock(nodes.Dir_out_mlock, nodes.Dir_out_mklck, 'p'); else nodeulock(&noden, mypid); if (noden.domain) free(noden.domain); return; } /* * Read all other mail filenames, get the timestamp and add them * to the memory array for later sort on filedate. */ while ((de = readdir(dp))) if ((strlen(de->d_name) == 11) && (strncasecmp(de->d_name,"mailpkt.",8) == 0)) { stat(de->d_name, &sbuf); Syslog('p', "Adding %s to filelist", de->d_name); fill_fdlist(&fdl, de->d_name, sbuf.st_mtime); } closedir(dp); sort_fdlist(&fdl); first = TRUE; while ((fname = pull_fdlist(&fdl)) != NULL) { if (first) { Syslog('+', "Pack unpacked mail for %s via %s", aka2str(nodenr), ascfnode(&noden, 0x1f)); if (!do_quiet) { printf("\rAdding netmail for %s ", ascfnode(&noden, 0x1f)); fflush(stdout); } first = FALSE; flushed = TRUE; } sprintf(pktfile, "%s/%s", temp, fname); if (strstr(fname, ".ddd")) flavor = 'd'; else if (strstr(fname, ".ccc")) flavor = 'c'; else if (strstr(fname, ".hhh")) flavor = 'h'; else flavor = 'o'; if (nodes.Session_out == S_DIR) { sprintf(arcfile, "%s/%08lx.pkt", nodes.Dir_out_path, sequencer()); } else { sprintf(arcfile, "%s", pktname(&noden, flavor)); } Syslog('P', "Outfile: %s", arcfile); Syslog('P', "Pktfile: %s", pktfile); /* * Now we must see if there is already mail in the outbound. * If that's the case, we must skip the .pkt header from the queue * because there is already a .pkt header also, append to the * outbound 2 bytes before the end of file, this is the zero word. */ if ((inf = fopen(pktfile, "r")) != NULL) { if (access(arcfile, R_OK) == -1) { ouf = fopen(arcfile, "w"); /* create new */ Syslog('p', "Create new %s", arcfile); } else { ouf = fopen(arcfile, "r+"); /* open R/W */ fseek(ouf, -2, SEEK_END); /* b4 0 word */ fseek(inf, 58, SEEK_SET); /* skip header */ Syslog('p', "Append to %s", arcfile); } if (ouf != NULL) { buf = malloc(16384); do { bread = fread(buf, 1, 16384, inf); fwrite(buf, 1, bread, ouf); } while (bread); free(buf); putc('\0', ouf); putc('\0', ouf); fsync(fileno(ouf)); fclose(ouf); fclose(inf); unlink(pktfile); } else { WriteError("$Can't open %s", arcfile); fclose(inf); } } /* * Change filemode so downlink has rights to the file. */ if (nodes.Session_out == S_DIR) chmod(arcfile, 0660); } /* * Now add the files for the node, information is in the .filelist * file, this tells the location of the file and what to do with * it after it is sent. */ sprintf(pktfile, "%s/.filelist", temp); if ((fp = fopen(pktfile, "r")) != NULL) { Syslog('+', "Adding files for %s via %s", aka2str(nodenr), ascfnode(&noden, 0x1f)); if (!do_quiet) { printf("\rAdding files for %s ", ascfnode(&noden, 0x1f)); fflush(stdout); flushed = TRUE; } buf = calloc(PATH_MAX + 1, sizeof(char)); while (fgets(buf, PATH_MAX, fp)) { Striplf(buf); Syslog('p', ".filelist: %s", buf); flavor = buf[0]; p = strchr(buf, ' '); p++; if (strncmp(p, "LEAVE ", 6) == 0) mode = LEAVE; else if (strncmp(p, "KFS ", 4) == 0) mode = KFS; else if (strncmp(p, "TFS ", 4) == 0) mode = TFS; else { WriteError("Syntax error in filelist \"%s\"", buf); mode = LEAVE; } p = strchr(p, ' '); p++; if (strncmp(p, "NOR ", 4) == 0) fdn = FALSE; else if (strncmp(p, "FDN ", 4) == 0) fdn = TRUE; else fdn = FALSE; p = strchr(p, ' '); p++; Syslog('p', "File attach (fdn=%s) %s to %s", fdn?"True":"False", p, ascfnode(&noden, 0x1f)); if (nodes.Session_out == S_DIRECT) { attach(noden, p, mode, flavor, fdn); } else if (nodes.Session_out == S_DIR) { sprintf(arcfile, "%s/%s", nodes.Dir_out_path, Basename(p)); if (mode == LEAVE) { /* * LEAVE file, so we copy this one. */ rc = file_cp(p, arcfile); Syslog('p', "file_cp(%s, %s) rc=%d", p, arcfile, rc); } else { /* * KFS or TFS, move file to node directory */ rc = file_mv(p, arcfile); Syslog('p', "file_mv(%s, %s) rc=%d", p, arcfile, rc); } chmod(arcfile, 0660); } Syslog('P', "%c %d %s", flavor, mode, pktfile); } free(buf); fclose(fp); unlink(pktfile); } /* * We are done, the queue is flushed, unlock the node. */ if (nodes.Session_out == S_DIR) remlock(nodes.Dir_out_mlock, nodes.Dir_out_mklck, 'p'); else nodeulock(&noden, mypid); if (noden.domain) free(noden.domain); free(temp); free(arcfile); free(pktfile); return; } /* * Flush outbound queue to real outbound. */ void flush_queue(void) { char *temp; struct dirent *de; DIR *dp; Syslog('+', "Flushing outbound queue"); if (enoughspace(CFG.freespace) == 0) { Syslog('+', "Low diskspace, not flushing outbound queue"); return; } IsDoing("Flush queue"); if (!do_quiet) { colour(LIGHTBLUE, BLACK); printf("Flushing outbound queue\n"); colour(CYAN, BLACK); } temp = calloc(PATH_MAX, sizeof(char)); sprintf(temp, "%s/foobar", CFG.out_queue); mkdirs(temp, 0750); if ((dp = opendir(CFG.out_queue)) == 0) { WriteError("$Can't open %s", CFG.out_queue); free(temp); return; } /* * The outbound queue contains subdirectories which actuallly hold the * queue outbound files. Process each found subdirectory. */ while ((de = readdir(dp))) { if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) { sprintf(temp, "%s/%s", CFG.out_queue, de->d_name); Syslog('p', "Queue directory %s", temp); flush_dir(de->d_name); if (chdir(CFG.out_queue)) WriteError("$Can't chdir to %s", CFG.out_queue); if (rmdir(temp)) WriteError("$Can't rmdir %s", temp); } } closedir(dp); free(temp); if (!do_quiet) { printf("\r \r"); fflush(stdout); } }