/* * fileart - file an article, given its temporary file name and its headers * * It may be desirable to, some day, prevent cross-postings across * "universes", where a universe might be "alt" or "comp,news". * * There are three classes of newsgroup for the purposes of filing: * "wanted" (in the active file and missing the "x" flag); * "not wanted" ("x"ed in active, or not in active and not matched by sys * file's subscription list for this machine), so ignore it; or * "don't know it" (not in active and matched by subscription list, * so file the article in junk once, iff there are no good groups). * junk *must* be in the active file or it's an error (ST_DROPPED), * but junk may have an "x" flag to prevent filing. * * Use the active file 'x' flag to snuff groups quietly, even when your * subscription list permits them, without filing in junk. */ #include #include #include #include #include "fixerrno.h" #include #include "libc.h" #include "news.h" #include "config.h" #include "control.h" #include "active.h" #include "fileart.h" #include "mkdirs.h" #include "headers.h" #include "article.h" #include "history.h" #include "ngmatch.h" #include "system.h" #define XREFDELIM ':' static long artnum; /* asgnartnum sets artnum */ static int goodngs; /* asgnartnum reads goodngs */ static boolean debug = NO; /* imports from news */ extern void prefuse(); /* forwards */ FORWARD void asgnartnum(), gotgoodng(), mkjunklink(), mklinks(); FORWARD boolean openorlink(), mkonelink(), tryartnum(); void filedebug(state) /* set debugging state */ boolean state; { debug = state; } /* * File in the spool directory the article in art & fill in art->a_files. * Generate Xref: header if needed (successfully cross-posted). * (Thus must be called before emitting any article body!) * * If a_unlink is true, there is a temp file, so openfirst should * be false, and vice versa. * * If openfirst (!art->a_unlink) is true, fill in a_tmpf with the name of * the first link, fopen it (into art->a_artf), and make any remaining links. * If openfirst is false, just make links to a_tmpf, which is already * open as art->a_artf. openfirst means "Newsgroups:" was seen in time. * * Could make empty files for cross-posting "links" here, but make copies into * them later. This sort of thing is needed on old SysVs with NEWSARTS split * across partitions (and apparently on Plan 9). */ void fileart(art) register struct article *art; { register boolean openfirst = !art->a_unlink; int junkgroups = 0; /* count "junked" groups */ char artnumstr[MAXCOMP]; /* article number in ascii */ extern boolean genxref; /* TODO: move into a header */ if (art->a_filed) return; /* don't file twice */ artnum = 0; goodngs = 0; mklinks(art, openfirst, artnumstr, &junkgroups); mkjunklink(art, openfirst, artnumstr, &junkgroups); /* -g or article crossposted, and article is open? */ if ((genxref && goodngs > 0 || goodngs > 1) && art->a_artf != NULL) emitxref(art); } /* * extract list of newsgroups (and possibly article numbers) from article * headers and history file, as options indicate. If article numbers are * being dictated by incoming Xref: or old history entry, the article numbers * will be attached to the end of the group names by a colon as in Xref: * (e.g. comp.lang.c:25780 general:12). */ char * extngs(art) register struct article *art; { register char *ngs = NULL, *site, *groups; extern boolean blvxref, dupsokay; /* TODO: put in header */ extern char *blvsite, *dupsite; /* TODO: put in header */ if (blvxref) { if (art->h.h_xref == NULL) (void) fprintf(stderr, "%s: no Xref: in believe-Xref mode\n", progname); else { for (site = groups = skipsp(art->h.h_xref); *groups != '\0' && isascii(*groups) && !isspace(*groups); groups++) ; /* skip over site name */ if (*groups != '\0') *groups++ = '\0'; /* terminate site name */ groups = skipsp(groups); if (!STREQ(site, blvsite)) (void) fprintf(stderr, "%s: article received from site `%s', not `%s' in believe-Xref mode\n", progname, site, blvsite); else ngs = groups; /* site is cool; rest is ngs */ } } else if (dupsokay) { groups = gethistory(art->h.h_msgid); /* TODO: free this */ if (groups == NULL) (void) fprintf(stderr, "%s: no history entry in duplicate-feed mode\n", progname); else if ((groups = findfiles(groups)) == NULL) (void) fprintf(stderr, "%s: expired history entry in duplicate-feed mode\n", progname); else { site = strsvto(art->h.h_path, '!'); if (!STREQ(site, dupsite)) (void) fprintf(stderr, "%s: article received from site `%s', not `%s' in duplicate-feed mode\n", progname, site, dupsite); else /* convert group/art list to group:art list */ for (ngs = groups; *groups != '\0'; groups++) if (*groups == FNDELIM) *groups = XREFDELIM; free(site); } } else { groups = art->h.h_ctlcmd != NULL? CONTROL: art->h.h_ngs; /* NCMP */ if (strchr(groups, XREFDELIM) != NULL) (void) fprintf(stderr, "%s: colon not permitted in Newsgroups: list\n", progname); else ngs = groups; } if (ngs == NULL) art->a_status |= ST_DROPPED; /* bummer */ else /* convert any spaces to commas for fileart */ for (groups = ngs; *groups != '\0'; groups++) if (*groups == ' ') *groups = NGSEP; return ngs; } /* * Store in spooldir. Link temp file to spooldir/ng/article-number * for each ng. Control messages go in CONTROL, never in all.all.ctl. */ STATIC void mklinks(art, openfirst, artnumstr, junkgroupsp) register struct article *art; boolean openfirst; char *artnumstr; int *junkgroupsp; { register char *ngs = extngs(art), *ng; register char *comma, *numb; if (art->a_status&ST_REFUSED) (void) fprintf(stderr, "%s: mklinks called with ST_REFUSED set (can't happen)\n", progname); for (; ngs != NULL; ngs = comma) { STRCHR(ngs, NGSEP, comma); if (comma != NULL) *comma = '\0'; /* will be restored below */ numb = strchr(ngs, XREFDELIM); if (numb != NULL) /* number supplied? */ *numb++ = '\0'; /* cleave number from group */ /* * Map group names in Newsgroups: header to local group * names for filing. */ ng = realngname(ngs); if (ng == NULL) ng = strsave(ngs); /* attempt to file */ asgnartnum(art, openfirst, ng, numb, artnumstr); /* * If no such group in active or link failed, and the group * wasn't 'x'ed in active, but our subscription list permits * this group, then set flag to file it under "junk" later. */ if ((artnum < 1 || art->a_status != ST_OKAY) && art->a_status != ST_REFUSED && ngpatmat(oursys()->sy_trngs, ng)) ++*junkgroupsp; /* * If article # was assigned & link succeeded, * update art->a_files list for history. */ if (artnum >= 1 && art->a_status == ST_OKAY) gotgoodng(art, ng, artnumstr); free(ng); if (numb != NULL) /* number supplied? */ *--numb = XREFDELIM; /* restore lost byte */ if (comma != NULL) *comma++ = NGSEP; /* step past comma */ /* asgnartnum refused just this ng */ art->a_status &= ~ST_REFUSED; } } /* * File once in "junk" iff no ngs were filed due to absence from * active, but some were permitted by sys. This will make one junk * link, no matter how many bad groups, and only if all are bad * (e.g. rec.drugs,talk.chew-the-fat). */ STATIC void mkjunklink(art, openfirst, artnumstr, junkgroupsp) register struct article *art; boolean openfirst; char *artnumstr; int *junkgroupsp; { if (goodngs != 0) return; /* shouldn't be here, with valid groups */ if (*junkgroupsp > 0) { /* * All groups were "junked". Try to file this article in * junk. */ asgnartnum(art, openfirst, JUNK, (char *)NULL, artnumstr); if (artnum >= 1 && art->a_status == ST_OKAY) { gotgoodng(art, JUNK, artnumstr); art->a_status |= ST_JUNKED; } else /* couldn't file article in junk. why? */ if (art->a_status&ST_REFUSED) { /* junk is 'x'ed */ prefuse(art); (void) printf( "no known groups in `%s' and %s group is excluded in active\n", art->h.h_ngs, JUNK); } else { /* junk is missing? */ static boolean warned = NO; art->a_status |= ST_REFUSED|ST_DROPPED; if (!warned) { warned = YES; (void) fprintf(stderr, "%s: can't file in %s group; is it absent from active?\n", progname, JUNK); } prefuse(art); (void) printf( "no known groups in `%s' and no %s group\n", art->h.h_ngs, JUNK); } } else { extern boolean histreject; /* * Groups were permitted by subscription list, but all * were 'x'ed in active, or otherwise refused. */ if (histreject) history(art, NOLOG); prefuse(art); (void) printf("all groups `%s' excluded in active\n", art->h.h_ngs); art->a_status |= ST_REFUSED; } } /* * Append ng/artnumstr to art's list of files, and bump goodngs. */ STATIC void gotgoodng(art, ng, artnumstr) struct article *art; char *ng, *artnumstr; { ++goodngs; histupdfiles(art, ng, artnumstr); } /* * Assign a permanent name and article number to the temporary name * art->a_tmpf in newsgroup "ng" & store the ascii form of the article * number into "artnumstr", returning the article number in "artnum". * If numb is non-null, it's the ascii article number to file under. * * If openfirst is true and goodngs is zero, set inname to artname, * fopen artname and store the result in art->a_artf. */ STATIC void asgnartnum(art, openfirst, ng, numb, artnumstr) struct article *art; boolean openfirst; /* open first link? */ register char *ng; /* read-only */ char *numb, *artnumstr; { register char *slashng; /* a group, slashed */ /* field active 'x' flag: don't file this group, quietly */ if (unwanted(ng)) { artnum = -1; art->a_status |= ST_REFUSED; return; } slashng = strsave(ng); mkfilenm(slashng); /* relative to spooldir */ if (numb != NULL) { /* number supplied? */ artnum = atol(numb); /* believe number */ if (!tryartnum(art, openfirst, slashng, artnumstr)) { (void) fprintf(stderr, "%s: article # %ld in directory `%s' supplied but occupied!\n", progname, artnum, slashng); art->a_status |= ST_DROPPED; } } else while ((artnum = nxtartnum(ng)) >= 1) if (tryartnum(art, openfirst, slashng, artnumstr)) break; free(slashng); } /* * Construct a link name (slashng/artnum) for this article, * and try to link art to it. * * We changed directory to spooldir in main(), so the generated name * is relative to spooldir, therefore artname can be used as is. * * Return value is identical to mkonelink's. */ STATIC boolean tryartnum(art, openfirst, slashng, artnumstr) register struct article *art; boolean openfirst; register char *slashng; char *artnumstr; /* side-effect returned here */ { register char *artname; /* article file name */ register boolean ret; (void) sprintf(artnumstr, "%ld", artnum); artname = nemalloc((unsigned) (strlen(slashng) + STRLEN(SFNDELIM) + strlen(artnumstr) + SIZENUL)); (void) strcpy(artname, slashng); (void) strcat(artname, SFNDELIM); (void) strcat(artname, artnumstr); #ifdef notdef char *tartname = strsave(artfile(artname)); free(artname); artname = tartname; #endif ret = mkonelink(art, artname, openfirst); free(artname); return ret; } /* * Try to link art to artname. * If the attempt fails, maybe some intermediate directories are missing, * so create any missing directories and try again. If the second attempt * also fails, look at errno; if it is EEXIST, artname already exists * (presumably because the active file is out of date, or the existing * file is a directory such as net/micro/432), so indicate that higher * levels should keep trying, otherwise we are unable to create the * necessary directories, so complain and set bad status in art. * * Returns YES iff there is no point in trying to file this article again, * usually because it has been successfully filed, but sometimes because * the necessary directories cannot be made. */ STATIC boolean mkonelink(art, artname, openfirst) register struct article *art; register char *artname; boolean openfirst; { if (openorlink(artname, art, openfirst, goodngs)) return YES; else { (void) mkdirs(artname, getuid(), getgid()); if (openorlink(artname, art, openfirst, goodngs)) return YES; else if (errno != EEXIST) { warning("can't link to `%s'", artname); art->a_status |= ST_DROPPED; return YES; /* hopeless - give up */ } else return NO; } } /* * Try to make a link of art (actually art->a_tmpf) to artname. * If no links have been made yet, record and open artname iff no * link yet exists to that name (by any file, to avoid overwriting * existing articles, e.g. due to an out-of-date active file). * If links already exist, try to make artname a link to the first link * (art->a_tmpf). * * Liberally sprinkled with debugging output, as this is the heart * of article filing. */ STATIC boolean openorlink(artname, art, openfirst, goodngcnt) register char *artname; register struct article *art; boolean openfirst; /* open art's first link? */ int goodngcnt; /* count of good news groups */ { register boolean worked; /* open or link worked? */ errno = 0; /* paranoia */ if (openfirst && goodngcnt == 0) { if (debug) (void) fprintf(stderr, "opening `%s'... ", artname); nnfree(&art->a_tmpf); art->a_tmpf = strsave(artname); art->a_artf = fopenexcl(art->a_tmpf); worked = art->a_artf != NULL; } else { if (debug) (void) fprintf(stderr, "linking `%s' to `%s'... ", art->a_tmpf, artname); worked = link(art->a_tmpf, artname) == 0; if (!worked) /* * If art->a_tmpf really *is* a temporary name (because * the whole header didn't fit in core), then this will * produce a symbolic link to a non-existent name when * art->a_tmpf is unlinked, which will be soon. * Moral: don't run Eunice on your PDP-11. */ worked = symlink(fullartfile(art->a_tmpf), artname)==0; } if (debug) if (worked) (void) fprintf(stderr, "success.\n"); else warning("failed.", ""); return worked; } #if 0 /* called after copyart */ mkcopies(art) register struct article *art; { if (any links failed & empty files could be made) for (each failed link) copy open 1st link to this one; } #endif