/*- * Copyright (c) 1996 * David Parsons. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by David Parsons * (orc@pell.chi.il.us) * 4. My name may not be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ static char rcsid[] = "$Header: /home/orc/src/CVS/automod/automod.c,v 1.22 1996/11/05 16:02:29 orc Exp $"; /* * automod: moderate articles via a bot. */ #include "automod.h" #include #include #include #include #include #include #include #include #include #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif /* * header lines that are of some importance */ struct _header { char *line; int lsize; char *value; int flag; #define HDR_OPTIONAL 0 #define HDR_REQUIRED 01 #define HDR_DISCARD 02 #define HEADER(name,flag) { name, 0, 0, flag } } headers[] = { HEADER("From:", HDR_REQUIRED), #define FROM (headers) HEADER("Newsgroups:", HDR_REQUIRED), #define NEWSGROUPS (headers+1) HEADER("References:", HDR_OPTIONAL), #define REFERENCES (headers+2) HEADER("Subject:", HDR_REQUIRED), #define SUBJECT (headers+3) HEADER("Path:", HDR_OPTIONAL), #define PATH (headers+4) HEADER("Date:", HDR_REQUIRED), HEADER("Message-Id:", HDR_OPTIONAL), #define MESSAGE_ID (headers+6) HEADER("X-HAVE-READ-FAQ:", HDR_OPTIONAL), #define HAVE_READ_FAQ (headers+7) HEADER("X-Reply-To:", HDR_OPTIONAL), #define XREPLYTO (headers+8) HEADER("Reply-To:", HDR_OPTIONAL), #define REPLY_TO (headers+9) HEADER("Keywords:", HDR_OPTIONAL), HEADER("To:", HDR_DISCARD), HEADER("Received:", HDR_DISCARD), HEADER("Approved:", HDR_DISCARD), HEADER("Sender:", HDR_DISCARD), HEADER("From ", HDR_DISCARD), }; #define NR_HEADERS (sizeof headers/sizeof headers[0]) /* Macros to do error-checking arglist construction */ #define XADDARG(arg,siz) (addarg(&argc,&argv,arg,siz) == -1)\ ? critical_error("building exec list", errno)\ : 0 #define XADDSTR(arg) XADDARG(arg,-1) /* * the various database files that we use to autoapprove, * handle pending posters, and check for suspicious crossposting */ GDBM_FILE approve, pending, crosspost; /* ** Variables to hold configurable parameters. */ char *SubmissionAddr = NULL; /* submission address for the group */ char *AdminAddr = NULL; /* administrative address for the group */ char *Approval = NULL; /* string to put in the approval line */ char *FirstPostKeyword = NULL; /* the keyword to be used by first time posters */ int LogSubmissions; /* Should we log the entire submission? */ char *newsgroup; /* what newsgroup is this for? */ FILE *log; /* the incoming mail log */ long headermask; /* missing headers, for reject(..., DENY_HEADERS) */ char *critical_message; /* error messages for reject(..., DENY_CRITICAL) */ int critical_errno; char rejecting = 0; /* set nonzero when we're rejecting an article, so * panics won't cause infinite looping */ char *fromAddress = 0; /* From: line on the mail message */ char *genericFrom = 0; /* Sender's addr with first domain element removed */ char delurk_flag = 0; /* subject is prefixed with the Keyword */ static int lineNumber = 0; /* Current line number in config file. */ /* * duplicate a string, lowercasing it as we go. */ static char * strlwrdup(char *originalstring) { char *tmp; if ((tmp = strdup(originalstring)) != (char*)0) strlwr(tmp); return tmp; } /* strlwrdup */ /* * build a pathname from the newsgroup we're moderating */ static char * PATHNAME(char *path) { static char bfr[4096]; sprintf(bfr, path, newsgroup); return bfr; } /* PATHNAME */ /* * addpending() adds someone to the PENDING database */ addpending(char *name, char *subject) { datum key, value; syslog(LOG_DEBUG, "addpending('%s', '%s')", name, subject); if (pending = gdbm_open(PATHNAME(PENDING), 0, GDBM_WRCREAT, 0660, 0)) { key.dptr = strlwrdup(name); key.dsize = strlen(name); value.dptr = subject; value.dsize = 1+strlen(subject); gdbm_store(pending, key, value, GDBM_REPLACE); gdbm_close(pending); } } /* addpending */ /* * droppending() drops someone from the PENDING database */ droppending(char *name) { datum key; syslog(LOG_DEBUG, "droppending('%s')", name); if (pending = gdbm_open(PATHNAME(PENDING), 0, GDBM_WRITER, 0660, 0)) { key.dptr = strlwrdup(name); key.dsize = strlen(name); gdbm_delete(pending, key); gdbm_close(pending); } } /* droppending */ /* * getheaders() pulls in some headers, writing them out to a spoolfile */ int getheaders(FILE *ff) { char *bfr; register i; struct _header *p; /* throw away all earlier headers */ for (i=0; ivalue) { free(p->value); p->value = 0; } } /* slurp up all the headers we can eat */ while ((bfr=getline(stdin)) && bfr[0] != 0) { if (LogSubmissions) fprintf(log, "%s\n", bfr); for (i=0; iline, bfr, p->lsize) == 0) { if (p->value) free(p->value); p->value = strdup(Trim(p->lsize + bfr)); if (p->flag & HDR_DISCARD) goto nextline; if (p == PATH) { fprintf(ff, "%s there.is.no.cabal", PATH->line, Trim(PATH->lsize+bfr)); } else if (p == SUBJECT) { if (strncasecmp(Trim(SUBJECT->lsize+bfr), FirstPostKeyword, 6) == 0) { delurk_flag = 1; fprintf(ff, "%s %s", SUBJECT->line, Trim(6+Trim(SUBJECT->lsize+bfr))); } else { delurk_flag = 0; fputs(bfr, ff); } } else fputs(bfr, ff); putc('\n', ff); goto nextline; } } /* copy unknown headers through */ fputs(bfr, ff); putc('\n', ff); nextline: ; } if (LogSubmissions) putc('\n', log); return bfr ? 1 : 0; } /* getheaders */ /* * complete(bits) checks to see if all the required headers have been read */ int complete(long *bits) { register i; register struct _header *p; *bits = 0; for (i=0; iflag & HDR_REQUIRED) && (p->value == (char*)0)) (*bits) |= (1<value) fromAddress = getfromaddress(strdup(REPLY_TO->value)); else if (XREPLYTO->value) fromAddress = getfromaddress(strdup(XREPLYTO->value)); else if (FROM->value) fromAddress = getfromaddress(strdup(FROM->value)); if (LogSubmissions) fprintf(log, "[fromAddress is %s]\n\n", fromAddress ? fromAddress : "{null}"); /* ** Some users are connected routers that can connect them through a ** number of different machines within the same domain. Thus, the ** user has no control over the first element in his domain name and ** may end up with addresses such as `me@node1.iou.edu', ** `me@node2.iou.edu', etc. This means that he will look to automod like ** a first time poster each time he hits a new node. ** ** To get around this, the moderator can manually replace the user's ** address in the approval database with one containing a `generic' ** domain name. I.e. one in which the first element of the domain name ** is `*'. Thus `me@node1.iou.edu' and `me@node2.iou.edu' would both ** have the same generic address of `me@*.iou.edu'. ** ** If automod cannot find a match against the exact address then ** it will then attempt a match against the generic address. (Note that ** this won't help those people for whom the second or later element of ** their FQDN changes, but hopefully they are a small and docile group.) ** ** So we have to create a generic address from the sender's specific ** address just in case automod needs it. */ genericFrom = strdup(fromAddress); if (p1 = index(genericFrom, '@')) { if (*++p1) { *p1++ = '*'; if (p2 = index(p1, '.')) { /* ** We would like to simply do strcpy(p1, p2) below, but ** strcpy() is not guaranteed to work on overlapping strings. ** So the trickery below copies the corresponding text from ** the original FROM->value string. */ strcpy(p1, fromAddress + (p2 - genericFrom)); } else { *p1 = '\0'; } } } /* Supply a message-id if the article doesn't include one */ if (MESSAGE_ID->value == (char*)0) { time_t now; time(&now); fprintf(ff1, "Message-ID: <%lx-%s>\n", now, Approval); } /* Supply a path if the article doesn't include one */ if (PATH->value == (char*)0) fprintf(ff1, "Path: there.is.no.cabal\n"); /* Supply a Newsgroups: line if the article doesn't include one */ if (NEWSGROUPS->value == (char*)0) fprintf(ff1, "Newsgroups: %s\n", newsgroup); putc('\n', ff1); while ((c = getchar()) != EOF) { if (LogSubmissions) putc(c, log); putc(c, ff1); } if (LogSubmissions) { fputs("@OK ----\n", log); fclose(log); } fflush(ff1); rewind(ff1); return ff1; } /* retrieve */ /* * dopost() writes the file to the RNEWS program, adding the * appropriate "#! rnews " header to the article as it * goes */ dopost(FILE *ff) { FILE *rnews; register c; int rc; struct stat info; int approved_size = strlen("Approved: ") + strlen(Approval) + 1; if (rnews = popen(RNEWS, "w")) { register c; fstat(fileno(ff), &info); fprintf(rnews, "#! rnews %d\n", info.st_size+approved_size); rewind(ff); fprintf(rnews, "Approved: %s\n", Approval); while ((c=getc(ff)) != EOF) putc(c, rnews); if ((rc = pclose(rnews)) != 0) critical_error(RNEWS " failed", errno); } else critical_error("posting article", errno); } /* dopost */ /* * post()'s an article and adds the poster's email address to the autoapprove * database, giving the comment as the entry reason. */ post(FILE *ff, char *poster, char *comment) { datum key, value; syslog(LOG_DEBUG, "post(ff, '%s', '%s')", poster, comment); dopost(ff); if (approve = gdbm_open(PATHNAME(APPROVE), 0, GDBM_WRCREAT, 0660, 0)) { key.dptr = strlwrdup(poster); key.dsize = strlen(poster); value.dptr = comment; value.dsize = 1+strlen(comment); gdbm_store(approve, key, value, GDBM_REPLACE); gdbm_close(approve); } } /* * post_message() dumps a message from a file out to a FILE*, for * posting custom welcome and reject messages. * * It will also substitute the following macros within the file: * * Macro Replaced With * ----- ------------- * %G newsgroup * %K FirstPostKeyword * %S SubmissionAddr * %A AdminAddr * %% % */ int post_message(FILE *to, char *file) { register c; FILE *from; if (from = fopen(file, "r")) { while ((c = getc(from)) != EOF) { /* ** Do we have a macro to substitute? */ if (c == '%') { if ((c = getc(from)) == EOF) break; /* ** Yup. Now figure out which one and emit the appropriate text. ** ** Note that if the char after the `%' is not a recognized ** macro, then we discard it. That way users won't get into ** the habit of using unguarded `%'s which will make it easier ** to add more macros later on. ** ** The one exception to this is a `%' at the end of a line, in ** which case we will still emit the newline, just to keep ** the text properly formatted. */ switch (c) { case 'G': fputs(newsgroup, to); break; case 'K': fputs(FirstPostKeyword, to); break; case 'S': fputs(SubmissionAddr, to); break; case 'A': fputs(AdminAddr, to); break; case '\n': case '%': putc(c, to); break; } } else { putc(c, to); } } fclose(from); return 1; } return 0; } /* post_message */ /* * welcome_message() mails a WELCOME TO message to the caller */ void welcome_message(char *from) { char **argv = malloc(1); int argc = 0; int pip[2]; /* IPC between server and sendmail */ register c; int rc; int i; FILE *pipeline; char *p; pid_t pid; int found_message=0; syslog(LOG_DEBUG, "welcome_message('%s')", from); rejecting++; XADDSTR("sendmail"); XADDSTR("-i"); XADDSTR(from); XADDSTR((char*)0); if (pipe(pip) != 0) critical_error("creating pipe", errno); if ((pid=launch(SENDMAIL, argv, pip[0], EOF, EOF)) >= 0) { /* pipe everything to the child, then die */ if ((pipeline = fdopen(pip[1], "w")) != (FILE *)0) { fprintf(pipeline, "From: %s (%s automoderation robot)\n", SubmissionAddr, newsgroup); fprintf(pipeline, "Subject: Welcome to %s\n", newsgroup); fprintf(pipeline, "To: %s\n", from); fprintf(pipeline, "\n"); if (!post_message(pipeline, PATHNAME(WELCOME_MSG))) { fprintf(pipeline, "Ooops. This was intended to be a welcome message, but the following\n" "error happened when I tried to send it.\n" "\t%s\n", strerror(errno)); } if (ferror(pipeline)) critical_error("writing to " SENDMAIL, errno); fclose(pipeline); waitpid(pid, &rc, 0); /*exit(WEXITSTATUS(rc));*/ } else critical_error("writing to " SENDMAIL, errno); } else critical_error("launching " SENDMAIL, errno); } /* welcome_message */ /* * reject() returns the message to the sender with some short blurb * explaining why the message was not allowed. */ reject(FILE *ff1, FILE *ff2, char *poster, enum Denial denial_code) { char **argv = malloc(1); int argc = 0; int pip[2]; /* IPC between server and sendmail */ register c; int rc; int i; FILE *pipeline; char *p; pid_t pid; int found_message=0; syslog(LOG_DEBUG, "reject(ff1, ff2, '%s', %d)", poster, denial_code); rejecting++; XADDSTR("sendmail"); XADDSTR("-i"); XADDSTR(poster); XADDSTR((char*)0); if (pipe(pip) != 0) critical_error("creating pipe", errno); if ((pid=launch(SENDMAIL, argv, pip[0], EOF, EOF)) >= 0) { /* pipe everything to the child, then die */ if ((pipeline = fdopen(pip[1], "w")) != (FILE *)0) { fprintf(pipeline, "From: %s (%s automoderation robot)\n", SubmissionAddr, newsgroup); fprintf(pipeline, "Subject: Your post titled [%s]\n", SUBJECT->value); fprintf(pipeline, "To: %s\n", poster); fprintf(pipeline, "\n"); /* attempt to send messages from a file out, and if that * fails fall back on a tinned message */ switch (denial_code) { case DENY_CROSSPOST: found_message = post_message(pipeline, PATHNAME(CROSSPOST_MSG)); break; case DENY_FIRSTPOST: found_message = post_message(pipeline, PATHNAME(FIRSTPOST_MSG)); break; case DENY_REPOST: found_message = post_message(pipeline, PATHNAME(REPOST_MSG)); break; } if (!found_message) { fprintf(pipeline, "Your post has been rejected by the moderation robot, because\n"); switch (denial_code) { case DENY_CROSSPOST: fprintf(pipeline, "it is crossposted, and crossposted articles are forbidden here.\n"); break; case DENY_FIRSTPOST: fprintf(pipeline, "this appears to be your first post to the group %s. There is\n" "a way to get your message posted here, but if we told you, we'd\n" "have to kill you\n", newsgroup); break; case DENY_REPOST: fprintf(pipeline, "you haven't set the proper magic numbers. Have you read the FAQ\n" "yet?\n"); break; case DENY_HEADERS: fprintf(pipeline, "it was missing the following header lines:\n"); for (i=0; i>>\n"); if (ff1) { rewind(ff1); while ((c=getc(ff1)) != EOF) putc(c, pipeline); } if (ff2) { rewind(ff2); while ((c=getc(ff2)) != EOF) putc(c, pipeline); } } if (ferror(pipeline)) critical_error("writing to " SENDMAIL, errno); fclose(pipeline); waitpid(pid, &rc, 0); /*exit(WEXITSTATUS(rc));*/ } else critical_error("writing to " SENDMAIL, errno); } else critical_error("launching " SENDMAIL, errno); } /* * critical_error() is the all-purpose panic and inform everyone * error routine. It writes an error report on stdout, for sendmail * to return to the sender, and it also writes a error message to * the server moderator for their (hopefully) immediate attention */ critical_error(char *message, int errornumber) { FILE *mail; char sendmailCmd[300]; sprintf(sendmailCmd, "%s %s", SENDMAIL, AdminAddr); mail = popen(sendmailCmd, "w"); syslog(LOG_CRIT, errornumber ? "%s %s" : "%s", message, strerror(errornumber)); if (mail) { fprintf(mail, "From: %s (auto moderation bot)\n", newsgroup); fprintf(mail, "Subject: IT'S THE APOCOLYPSE!\n\n"); fprintf(mail, "We lost an incoming article: %s\n", message); if (errornumber) { fprintf(mail, "\terror %d\n", errornumber); fprintf(mail, "\t %s\n", strerror(errornumber)); } fprintf(mail, "\n-- the computer\n"); pclose(mail); } /* If we've picked up a From: address, send email there; otherwise * the errors will just vanish into the void */ if (fromAddress && !rejecting) { critical_message = message; critical_errno = errornumber; reject(NULL, NULL, fromAddress, DENY_CRITICAL); } thread_unlock(); exit(1); } /* ** In case of an error parsing the configuration file, open up a pipe ** to sendmail and keep it open so that multiple parse errors can be ** written to it. ** ** Call parseErrorDone() to send the message. ** ** Author: Dean Edmonds, 20 Oct 96 */ static FILE *parseMail = NULL; FILE * parseError() { if (parseMail == NULL) { char sendmailCmd[300]; /* ** Hopefully we've gotten far enough through the config file that ** we know the administrative address. If not then we've little ** choice but to use the default admin address. */ sprintf(sendmailCmd, "%s %s", SENDMAIL, (AdminAddr ? AdminAddr : DEFAULT_ADMIN_ADDR)); parseMail = popen(sendmailCmd, "w"); if (parseMail) { fprintf(parseMail, "From: %s (auto moderation bot)\n", newsgroup); fprintf(parseMail, "Subject: Errors parsing configuration file\n\n"); fprintf(parseMail, "While attempting to parse %s,\n", PATHNAME(CONFIG)); fprintf(parseMail, "the following errors occurred:\n\n"); } else { parseMail = stderr; } } return parseMail; } void parseErrorDone() { if (parseMail && (parseMail != stderr)) pclose(parseMail); } /* ** Parse a single parameter/value pair out of the config file. ** ** Note that the values stuffed into the pointers are to static buffers ** which will get overwritten on the next call, so they must be copied ** by the caller if he wishes to keep them around. ** ** Author: Dean Edmonds, 19 Oct 96 */ int parseParameter(FILE *configFile, char **parameterPtr, char **valuePtr) { char line[1024]; char *p; *parameterPtr = *valuePtr = ""; /* ** Keep getting lines from the file until we either find a valid parameter ** setting, or run out of lines. */ while (fgets(line, sizeof(line), configFile)) { lineNumber++; /* ** Skip over initial whitespace. */ p = line + strspn(line, " \t\n"); /* ** Ignore empty lines and any line beginning with `#'. */ if (*p && (*p != '#')) { /* ** Presumably we have the start of the parameter name. */ *parameterPtr = p; /* ** Find the end of the parameter name. */ p += strcspn(p, " \t\n="); /* ** Assuming that we have anything left to play with... ** the assignment and get to the start of the value. ** (Note that we are lazy and don't ensure that there really ** was an `=', so a simply space between the param and value ** will do.) */ if (*p) { /* ** Terminate the parameter name. */ *p++ = '\0'; /* ** Skip to the start of the value. (Note that the method used ** below will allow the user to specify `parameter value' with ** no `='. Big deal. I'm lazy and this is easy.) */ p += strspn(p, " \t\n="); *valuePtr = p; /* ** Skip to the end of the value and strip off any whitespace. */ for (p += strlen(p) - 1; index(" \t\n", *p); p--) *p = '\0'; } /* ** If it all worked out, then let the caller know the happy news. */ if (**parameterPtr && **valuePtr) return TRUE; /* ** I don't know what we got, but it wasn't legal. Let the moderator ** know about it. */ fprintf(parseError(), "Line %d: Syntax error.\n", lineNumber); } } /* ** The config file is all done. */ return FALSE; } #define TYPE_BOOL 1 #define TYPE_INT 2 #define TYPE_STR 3 /* ** Convert a string to the appropriate parameter type. ** ** Author: Dean Edmonds, 19 Oct 96 */ void stringToParameter(char *string, int parameterType, void *parameter) { switch (parameterType) { case TYPE_BOOL: *(int *)parameter = ((*string == 'Y') || (*string == 'y')); break; case TYPE_INT: *(int *)parameter = strtol(string); break; case TYPE_STR: *(char **)parameter = strdup(string); break; } } /* ** Convert a parameter value to a string. ** ** Author: Dean Edmonds, 19 Oct 96 */ char * parameterToString(int parameterType, void *parameter) { static char buff[20]; switch (parameterType) { case TYPE_BOOL: return (*(int *)parameter) ? "Y" : "N"; case TYPE_INT: sprintf(buff, "%d", *(int *)parameter); return buff; case TYPE_STR: return *(char **)parameter; } return ""; } /* ** Parse the configuration file. ** ** Author: Dean Edmonds, 19 Oct 96 */ void parseConfig(char *newsgroup) { FILE *configFile; char *parameter, *value; int i; static struct { char *name; int type; void *valuePtr; char *defaultValue; int set; } paramTbl[] = { { "LogSubmissions", TYPE_BOOL, &LogSubmissions, "Y", FALSE }, { "SubmissionAddr", TYPE_STR, &SubmissionAddr, NULL, FALSE }, { "AdminAddr", TYPE_STR, &AdminAddr, DEFAULT_ADMIN_ADDR, FALSE }, { "Approval", TYPE_STR, &Approval, DEFAULT_APPROVAL, FALSE }, { "Keyword", TYPE_STR, &FirstPostKeyword, DEFAULT_KEYWORD, FALSE } }; #define NUM_PARAMETERS (sizeof(paramTbl) / sizeof(*paramTbl)) /* ** Okay, let's find out how much trouble we're in. Do we have a ** configuration file out there? */ if (configFile = fopen(PATHNAME(CONFIG), "r")) { /* ** Oh good, we do. Let's scan through it and see what parameters it ** defines. */ while (parseParameter(configFile, ¶meter, &value)) { /* ** We've got a parameter/value pair, let's see who it is. */ for (i = 0; i < NUM_PARAMETERS; i++) { if (strcasecmp(paramTbl[i].name, parameter) == 0) { /* ** Found it! ** ** If we haven't already, set the parameter's value. */ if (!paramTbl[i].set) { stringToParameter(value, paramTbl[i].type, paramTbl[i].valuePtr); paramTbl[i].set = TRUE; } else { fprintf(parseError(), "Line %d: Duplicate value found " "for `%s'.\n", lineNumber, paramTbl[i].name); } break; } } /* ** If we didn't recognize the parameter, then we should log it ** as a problem. */ if (i == NUM_PARAMETERS) { fprintf(parseError(), "Line %d: Invalid parameter `%s'.\n", lineNumber, parameter); } } fclose(configFile); } /* ** Uh-oh. No dice on the config file. Better log it. */ else { fprintf(parseError(), "Could not read configuration file\n"); } /* ** If any parameters were not found in the config file, set them to their ** default values. */ for (i = 0; i < NUM_PARAMETERS; i++) { if (!paramTbl[i].set) { /* ** We create the default for the submission address by replacing ** all of the periods in the group name with hyphens and assuming ** that the host we are running on is the proper host for ** submissions. */ if (paramTbl[i].valuePtr == &SubmissionAddr) { char *p, *nodeReference, hostName[200]; p = SubmissionAddr = strdup(newsgroup); while (p = index(p, '.')) *p = '-'; /* ** If we can get the host name, then slap it onto the end. */ if (gethostname(hostName, 200) == 0) { p = (char *) malloc(strlen(SubmissionAddr) + strlen(hostName) + 2); sprintf(p, "%s@%s", SubmissionAddr, hostName); free(SubmissionAddr); SubmissionAddr = p; } } /* ** For all others, use the default from the param table. */ else { stringToParameter(paramTbl[i].defaultValue, paramTbl[i].type, paramTbl[i].valuePtr); } } } /* ** Send off the error messages (if any). */ parseErrorDone(); } /* * automod, in mortal flesh */ main(int argc, char **argv) { FILE *ff; datum key, value; char *p; int i; int is_pending = 0; setregid(getegid(),getegid()); setreuid(geteuid(),geteuid()); if (argc == 2) { newsgroup = strdup(argv[1]); } else { fprintf(stderr, "Usage: %s newsgroup\n", argv[0]); exit(1); } /* ** Get the configuration info. */ parseConfig(newsgroup); for (i=0; ivalue) { int crossposted_to=0; if (crosspost = gdbm_open(PATHNAME(XPOST), 0, GDBM_READER, 0, 0)) { for (p=strtok(NEWSGROUPS->value,","); p; p=strtok((char*)0, ",")) { crossposted_to++; if (strcmp(p, newsgroup) == 0) continue; key.dptr = strlwrdup(p); key.dsize = strlen(p); value = gdbm_fetch(crosspost, key); if (value.dsize == 0 || value.dptr == 0) { reject(ff, NULL, fromAddress, DENY_CROSSPOST); thread_unlock(); exit(0); } } gdbm_close(crosspost); if (crossposted_to > 2) { reject(ff, NULL, fromAddress, DENY_CROSSPOST); thread_unlock(); exit(0); } } } if (approve = gdbm_open(PATHNAME(APPROVE), 0, GDBM_READER, 0660, 0)) { key.dptr = strlwrdup(fromAddress); key.dsize = strlen(fromAddress); value = gdbm_fetch(approve, key); /* ** If we didn't find the user by his exact address, then let's try ** using his generic address. (See retrieve() for more info on ** generic addresses.) ** ** Note that we don't do a similar check against the pending database ** later on, because generic addresses are always entered by the ** moderator by hand and only into the approved database. */ if ((value.dsize == 0) || (value.dptr == NULL)) { key.dptr = strlwrdup(genericFrom); key.dsize = strlen(genericFrom); value = gdbm_fetch(approve, key); } gdbm_close(approve); if ((value.dsize > 0) && value.dptr) { syslog(LOG_DEBUG, "Approved from %s", fromAddress); dopost(ff); thread_unlock(); exit(0); } syslog(LOG_DEBUG, "%s not in %s", fromAddress, PATHNAME(APPROVE)); } /* Find out now if the this poster is in the pending database; * this will alter what sort of messages they get sent when * messages are rejected */ if (pending = gdbm_open(PATHNAME(PENDING), 0, GDBM_READER, 0660, 0)) { syslog(LOG_DEBUG, "Pending is OPEN: look for [%s]", fromAddress); key.dptr = strlwrdup(fromAddress); key.dsize = strlen(key.dptr); value = gdbm_fetch(pending, key); gdbm_close(pending); is_pending = (value.dsize > 0) && (value.dptr != 0); syslog(LOG_DEBUG, "value is (%d)%.*s", value.dsize, value.dptr ? value.dsize : strlen("{null}"), value.dptr ? value.dptr : "{null}"); } if (delurk_flag) { droppending(fromAddress); if (is_pending) post(ff, fromAddress, "Resubmit with Delurk:"); else { post(ff, fromAddress, "Delurk"); welcome_message(fromAddress); } thread_unlock(); exit(0); } if (HAVE_READ_FAQ->value && strcasecmp(HAVE_READ_FAQ->value, "YES") == 0) { droppending(fromAddress); post(ff, fromAddress, "X-Have-Read-FAQ"); thread_unlock(); exit(0); } addpending(fromAddress, SUBJECT->value); if (is_pending) reject(ff, NULL, fromAddress, DENY_REPOST); else { welcome_message(fromAddress); reject(ff, NULL, fromAddress, DENY_FIRSTPOST); } thread_unlock(); exit(0); } /* main */