* This software is Copyright (c) 1986 by Rick Adams.
* Permission is hereby granted to copy, reproduce, redistribute or
* otherwise use this software as long as: there is no monetary
* profit gained specifically from the use or reproduction or this
* software, it is not sold, rented, traded or otherwise marketed, and
* this copyright notice is included prominently in any copy
* The author make no claims as to the fitness or correctness of
* this software for any use whatsoever, and it is provided as is.
* Any use of this software is at the user's own risk.
* inews - insert, receive, and transmit news articles.
static char *SccsId
= "@(#)inews.c 2.61 3/19/86";
/* local defines for inews */
#define OPTION 0 /* pick up an option string */
#define STRING 1 /* pick up a string of arguments */
#define UNKNOWN 0001 /* possible modes for news program */
#define UNPROC 0002 /* Unprocessed input */
#define PROC 0004 /* Processed input */
#define CANCEL 0010 /* Cancel an article */
#define CREATENG 0020 /* Create a new newsgroup */
char forgedname
[NAMELEN
]; /* A user specified -f option. */
/* Fake sys line in case they forget their own system */
struct srec dummy_srec
= { "MEMEME", "", "all", "", "" };
char *Progname
= "inews"; /* used by xerror to identify failing program */
struct { /* options table. */
char optlet
; /* option character. */
char filchar
; /* if to pickup string, fill character. */
int flag
; /* TRUE if have seen this opt. */
int oldmode
; /* OR of legal input modes. */
int newmode
; /* output mode. */
char *buf
; /* string buffer */
} *optpt
, options
[] = { /*
optlet filchar flag oldmode newmode buf */
't', ' ', FALSE
, UNPROC
, UNKNOWN
, header
.title
,
'n', NGDELIM
, FALSE
, UNPROC
, UNKNOWN
, header
.nbuf
,
'd', '\0', FALSE
, UNPROC
, UNKNOWN
, header
.distribution
,
'e', ' ', FALSE
, UNPROC
, UNKNOWN
, header
.expdate
,
'p', '\0', FALSE
, UNKNOWN
,PROC
, filename
,
'f', '\0', FALSE
, UNPROC
, UNKNOWN
, forgedname
,
'F', ' ', FALSE
, UNPROC
, UNKNOWN
, header
.followid
,
'c', '\0', FALSE
, UNKNOWN
,CANCEL
, filename
,
'C', '\0', FALSE
, UNKNOWN
,CREATENG
, header
.nbuf
,
#define hflag options[9].flag
'h', '\0', FALSE
, UNPROC
, UNKNOWN
, filename
,
#define oflag options[10].flag
'o', '\0', FALSE
, UNPROC
, UNKNOWN
, header
.organization
,
#define Mflag options[11].flag
'M', '\0', FALSE
, UNPROC
, UNKNOWN
, filename
,
'a', '\0', FALSE
, UNPROC
, UNKNOWN
, header
.approved
,
'\0', '\0', 0, 0, 0, (char *)NULL
* Matt Glickman glickman@ucbarpa.Berkeley.ARPA
* Mark Horton mark@cbosgd.UUCP
* Stephen Daniels swd@mcnc.UUCP
* Tom Truscott trt@duke.UUCP
* IHCC version adapted by:
* Larry Marek larry@ihuxf.UUCP
int state
; /* which type of argument to pick up */
int tlen
, len
; /* temps for string processing routine */
register char *ptr
; /* pointer to rest of buffer */
int filchar
; /* fill character (state = STRING) */
char *user
= NULL
, *home
= NULL
; /* environment temps */
struct passwd
*pw
; /* struct for pw lookup */
struct group
*gp
; /* struct for group lookup */
FILE *mfd
; /* mail file file-descriptor */
char cbuf
[BUFLEN
]; /* command buffer */
/* uuxqt doesn't close all it's files */
for (i
= 3; !close(i
); i
++)
/* set up defaults and initialize. */
ptr
= rindex(*argv
, '/');
if (!strncmp(ptr
+1, "rnews", 5)) {
header
.title
[0] = header
.nbuf
[0] = filename
[0] = '\0';
/* check for existence of special files */
if (!rwaccess(ARTFILE
)) {
mfd
= mailhdr((struct hbuf
*)NULL
, exists(ARTFILE
) ? "Unwritable files!" : "Missing files!");
fprintf(mfd
,"System: %s.%s\n\nThere was a problem with %s!!\n", LOCALSYSNAME
, FULLSYSNAME
, ARTFILE
);
fprintf(mfd
,"System: %s\n\nThere was a problem with %s!!\n", FULLSYSNAME
, ARTFILE
);
(void) sprintf(cbuf
, "touch %s;chmod 666 %s", ARTFILE
, ARTFILE
);
fprintf(mfd
, "The problem has been taken care of.\n");
fprintf(mfd
, "Corrective action failed - check suid bits.\n");
mfd
= mailhdr((struct hbuf
*)NULL
, exists(ACTIVE
) ? "Unwritable files!" : "Missing files!");
fprintf(mfd
,"System: %s.%s\n\nThere was a problem with %s!!\n", LOCALSYSNAME
, FULLSYSNAME
, ARTFILE
);
fprintf(mfd
, "System: %s\n\nThere was a problem with %s!!\n", FULLSYSNAME
, ACTIVE
);
(void) sprintf(cbuf
, "touch %s;chmod 666 %s", ACTIVE
, ACTIVE
);
fprintf(mfd
, "The problem has been taken care of.\n");
fprintf(mfd
, "Corrective action failed - check suid bits.\n");
SigTrap
= FALSE
; /* true if a signal has been caught */
(void) signal(SIGHUP
, onsig
);
(void) signal(SIGINT
, onsig
);
savmask
= umask(N_UMASK
); /* set up mask */
if (uid
== 0 && duid
== 0) {
* Must go through with this kludge since
* some systems do not honor the setuid bit
* when root invokes a setuid program.
if ((pw
= getpwnam(NEWSUSR
)) == NULL
)
xerror("Cannot get NEWSU pw entry");
if ((gp
= getgrnam(NEWSGRP
)) == NULL
)
xerror("Cannot get NEWSG gr entry");
* We force the use of 'getuser()' to prevent forgery of articles
* by just changing $LOGNAME
if (isatty(fileno(stderr
))) {
if ((user
= getenv("USER")) == NULL
)
user
= getenv("LOGNAME");
if ((home
= getenv("HOME")) == NULL
)
if (user
== NULL
|| home
== NULL
)
if (username
== NULL
|| username
[0] == 0) {
username
= AllocCpy(user
);
userhome
= AllocCpy(home
);
++argv
; /* skip first arg, which is prog name. */
xerror("Bad option string \"%s\"", *argv
);
while (*++*argv
!= '\0') {
for (optpt
= options
; optpt
->optlet
!= '\0'; ++optpt
) {
if (optpt
->optlet
== **argv
)
/* unknown option letter */
fprintf(stderr
, "usage: inews -t title");
fprintf(stderr
, " [ -n newsgroups ]");
fprintf(stderr
, " [ -e expiration date ]\n");
fprintf(stderr
, "\t[ -f sender]\n\n");
if (optpt
->flag
== TRUE
|| (mode
!= UNKNOWN
&&
(mode
&optpt
->oldmode
) == 0)) {
xerror("Bad %c option", **argv
);
filchar
= optpt
->filchar
;
argv
++; /* done with this option arg. */
* Pick up a piece of a string and put it into
* the appropriate buffer.
argc
++; /* uncount this arg. */
if ((tlen
= strlen(*argv
)) >= len
)
xerror("Argument string too long");
(void) strcpy(ptr
, *argv
++);
* ALL of the command line has now been processed. (!)
tty
= isatty(fileno(stdin
));
/* This code is really intended to be replaced by the control message. */
f
= xfopen(filename
, "r");
(void) hread(&header
, f
, TRUE
);
p
= index(header
.path
, ' ');
if (strncmp(username
, p
, strlen(username
))
&& uid
!= ROOTID
&& uid
!= geteuid() && uid
)
xerror("Not contributor");
ptr
= index(header
.nbuf
, '\0');
(void) nstrip(header
.title
);
(void) nstrip(header
.expdate
);
(void) nstrip(header
.followid
);
(void) hread(&header
, stdin
, FALSE
);
/* there are certain fields we won't let him specify. */
(void) strcpy(forgedname
, header
.from
);
header
.subdate
[0] = '\0';
if (header
.organization
[0] == '\0' && !Mflag
) {
strncpy(header
.organization
, MYORG
, BUFLEN
);
if (strncmp(header
.organization
, "Frobozz", 7) == 0)
header
.organization
[0] = '\0';
if (ptr
= getenv("ORGANIZATION"))
strncpy(header
.organization
, ptr
, BUFLEN
);
* Note that the organization can also be turned off by
* setting it to the null string, either in MYORG or
* $ORGANIZATION in the environment.
if (header
.organization
[0] == '/') {
mfd
= fopen(header
.organization
, "r");
(void) fgets(header
.organization
, sizeof header
.organization
, mfd
);
header
.organization
[0] = '\0';
logerr("Couldn't open %s",
ptr
= index(header
.organization
, '\n');
sprintf(header
.path
, "%s!%s",
else if (!header
.path
[0]) {
(void) strcpy(header
.path
, forgedname
);
if ((p1
= strpbrk(header
.path
, "@ (<")) != NULL
)
if (!Mflag
&& !strpbrk(forgedname
, "@ (<"))
(void) sprintf(header
.from
,"%s@%s%s",
forgedname
, FULLSYSNAME
, MYDOMAIN
);
(void) strncpy(header
.from
, forgedname
, BUFLEN
);
(void) sprintf(header
.sender
, "%s@%s%s",
username
, FULLSYSNAME
, MYDOMAIN
);
gensender(&header
, username
);
/* Authorize newsgroups. */
(void) signal(SIGHUP
, SIG_IGN
);
(void) signal(SIGINT
, SIG_IGN
);
(void) signal(SIGQUIT
, SIG_IGN
);
if (hread(&header
, stdin
, TRUE
) == NULL
)
xerror("Inbound news is garbled");
log("Duplicate article %s rejected. Path: %s",
header
.ident
, header
.path
);
/* Easy way to make control messages, since all.all.ctl is unblessed */
if (mode
!= PROC
&& prefix(header
.title
, "cmsg ") && header
.ctlmsg
[0] == 0)
(void) strcpy(header
.ctlmsg
, &header
.title
[5]);
is_ctl
= mode
!= CREATENG
&&
(ngmatch(header
.nbuf
, "all.all.ctl,") || header
.ctlmsg
[0]);
fprintf(stderr
,"is_ctl set to %d\n", is_ctl
);
/* Must end in comma (NGDELIM) */
#define MODGROUPS "mod.all,all.mod,all.announce,"
if (ngmatch(header
.nbuf
, MODGROUPS
) && !header
.approved
[0]) {
mfd
= mailhdr(&header
, "Moderated newsgroup");
fprintf(mfd
, "This newsgroup is moderated, and cannot be posted to directly.\n");
fprintf(mfd
, "Please mail your article to the moderator for posting.\n");
while ((i
= getc(infp
)) != EOF
)
xerror("Unapproved moderated newsgroup.");
xerror("No title, ng %s from %s", header
.nbuf
,
(void) strcpy(header
.nbuf
, DFLTNG
);
if (uid
&& uid
!= ROOTID
&& fascist(user
, header
.nbuf
))
xerror("User %s is not authorized to post to newsgroup %s", user
, header
.nbuf
);
/* Do the actual insertion. */
* Only certain users are allowed to create newsgroups
if (uid
!= ROOTID
&& uid
!= geteuid() && uid
) {
fprintf(stderr
, "Please contact one of the local netnews people\n\tto create this group for you");
(void) sprintf(bfr
, "%s/inews -n %s.ctl -t cmsg newgroup %s -d local",
LIB
, header
.nbuf
, header
.nbuf
);
printf("Please type in a paragraph describing the new newsgroup.\n");
printf("End with control D as usual.\n");
char firstbufname
[BUFLEN
];
* Link ARTICLE into dir for ngname and update active file.
actfp
= xfopen(ACTIVE
, "r+");
if (fgets(afline
, sizeof afline
, actfp
) == NULL
) {
logerr("Can't fine \"%s\" in active file", ngname
);
return FALSE
; /* No such newsgroup locally */
if (prefix(afline
, ngname
)) {
(void) sscanf(afline
, "%s %ld", bfr
, &ngsize
);
if (strcmp(bfr
, ngname
) == 0) {
if (ngsize
< 0 || ngsize
> 99998) {
logerr("found bad ngsize %ld ng %s, setting to 1", ngsize
, bfr
);
(void) sprintf(bfr
, "%s/%ld", cp
, ngsize
+1);
if (vmslink(ARTICLE
, bfr
) == 0)
if (link(ARTICLE
, bfr
) == 0)
e
= errno
; /* keep log from clobbering it */
log("Cannot install article as %s: %s", bfr
, errmsg(errno
));
logerr("Link into %s failed (%s); check dir permissions.",
actfp
= fopen(ACTIVE
, "r+");
* This works around a bug in the 4.1bsd stdio
* on fseeks to non even offsets in r+w files
(void) fseek(actfp
, fpos
, 0);
/* Has to be same size as old because of %05d.
* This will overflow with 99999 articles.
fprintf(actfp
, "%s %05ld", ngname
, ngsize
+1);
xerror("Active file write failed");
if (firstbufname
[0] == '\0')
(void) strcpy(firstbufname
, bfr
);
(void) sprintf(bfr
, "%s/%ld ", ngname
, ngsize
+1);
* Localize for each newsgroup and broadcast.
struct srec srec
; /* struct for sys file lookup */
register char *nextref
= header
.xref
;
/* Fill up the rest of header. */
sprintf(bfr
,"%2.2d/%2.2d/%d %2.2d:%2.2d\t",
sprintf(bfr
,"%02d/%02d/%d %02d:%02d\t",
tm
->tm_mon
+1, tm
->tm_mday
, tm
->tm_year
,tm
->tm_hour
, tm
->tm_min
);
log("%s %s ng %s subj '%s' from %s",
mode
==PROC
? "received" : "posted",
header
.ident
, header
.nbuf
, header
.title
, header
.from
);
/* Clean up Newsgroups: line */
if (!is_ctl
&& mode
!= CREATENG
)
is_invalid
= ngfcheck(mode
== PROC
);
/* Write article to temp file. */
tfp
= xfopen(mktemp(ARTICLE
), "w");
logerr("No valid newsgroups found, moved to junk");
if (time((time_t *)0) > (cgtdate(header
.subdate
) + HISTEXP
) ){
logerr("Article too old, moved to junk");
exitcode
= control(&header
);
if (localize("control") && exitcode
!= 0)
if (s_find(&srec
, FULLSYSNAME
) == FALSE
) {
logerr("Cannot find my name '%s' in %s", FULLSYSNAME
, SUBFILE
);
(void) strncpy(nextref
, FULLSYSNAME
, BUFLEN
);
for (ptr
= nbuf
; *ptr
;) {
if (ngmatch(ptr
,srec
.s_nbuf
) || index(ptr
,'.') == NULL
){
(void) sprintf(--nextref
, " %s:%ld", ptr
, localize(ptr
));
if (firstbufname
[0] == '\0') {
logerr("Newsgroups in active, but not sys");
if (index(header
.nbuf
, NGDELIM
) == NULL
)
/* Part 1 of kludge to get around article truncation problem */
if ( (c
=getc(infp
)) != EOF
) {
if (c
== ' ' || c
== '\t') {
(void) sprintf(header
.numlines
, "%d",
if ((c
= getc(infp
)) != EOF
) {
/* Part 2 of kludge to get around article truncation problem */
if (c
== ' ' || c
== '\t' )
while (fgets(bfr
, BUFLEN
, infp
) != NULL
)
if (bfr
[strlen(bfr
)-1] != '\n')
xerror("Write failed for temp file");
/* article has passed all the checks, so work in background */
signal(SIGTTOU
, SIG_IGN
);
xxit(mode
== PROC
? 0 : exitcode
);
register int empty
= TRUE
;
tmpfp
= xfopen(mktemp(INFILE
), "w");
infp
= xfopen(filename
, "r");
while (!SigTrap
&& fgets(bfr
, BUFLEN
, stdin
) != NULL
) {
if (mode
== PROC
) { /* zap trailing empty lines */
if (empty
&& bfr
[0] == '#' && bfr
[2] == ':'
&& header
.nf_id
[0] == '\0'
&& header
.nf_from
[0] == '\0' ) {
(void) strcpy(header
.nf_id
, bfr
);
(void) nstrip(header
.nf_id
);
(void) fgets(bfr
, BUFLEN
, stdin
);
(void) strcpy(header
.nf_from
, bfr
);
(void) nstrip(header
.nf_from
);
(void) fgets(bfr
, BUFLEN
, stdin
);
if (header
.numlines
[0]) {
(void) sprintf(header
.numlines
, "%d", header
.intnumlines
);
/* Strip trailing " - (nf)" */
if ((cp
= rindex(header
.title
, '-')) != NULL
&& !strcmp(--cp
, " - (nf)"))
log("Stripped notes header on %s", header
.ident
);
/* Bandage for older versions of inews */
bfr
[1] == '\n' && !isascii(bfr
[0])) {
consec_newlines
++; /* count it, in case */
continue; /* but don't write it*/
/* foo! a non-empty line. write out all saved lines. */
while (consec_newlines
> 0) {
if (mode
!= PROC
&& tty
&& strcmp(bfr
, ".\n") == 0)
for (cp
= bfr
; c
= toascii(*cp
); cp
++) {
if (isprint(c
) || isspace(c
) || c
== '\b')
if (mode
!= PROC
&& linserted
> (linecount
-linserted
))
xerror("Article rejected: More included text than new text");
if (mode
!= PROC
&& !is_ctl
&& header
.sender
[0] == '\0') {
(void) sprintf(bfr
, "%s/%s", userhome
, ".signature");
if (access(bfr
, 4) == 0) {
if ((infp
= fopen(bfr
, "r")) == NULL
) {
"inews: \"%s\" left off (must be readable by \"inews\" owner)\n", bfr
);
while (fgets(sbuf
, sizeof sbuf
, infp
) != NULL
)
fprintf(stderr
,".signature not included (> 4 lines)\n");
fprintf(tmpfp
, "-- \n"); /* To separate */
while ((c
= getc(infp
)) != EOF
) {
xerror("write failed to temp file");
fprintf(stderr
, "Interrupt\n");
fwait(fsubr(newssave
, (char *) NULL
, (char *) NULL
));
log("Blown away by an interrupt %d", SigTrap
);
fprintf(stderr
, "EOT\n");
infp
= fopen(INFILE
, "r");
if (header
.numlines
[0]) {
* Check line count if there's already one attached to
* the article. Could make this a fatal error -
* throwing it away if it got chopped, in hopes that
* another copy will come in later with a correct
* line count. But that seems a bit much for now.
if (linecount
!= header
.intnumlines
) {
xerror("%s rejected. linecount expected %d, got 0", header
.ident
, header
.intnumlines
);
if (linecount
> header
.intnumlines
||
linecount
+consec_newlines
< header
.intnumlines
)
log("linecount expected %d, got %d", header
.intnumlines
, linecount
+consec_newlines
);
/* adjust count for blank lines we stripped off */
header
.intnumlines
-= consec_newlines
;
if (header
.intnumlines
< 0 )
header
.intnumlines
= 0; /* paranoia */
(void) sprintf(header
.numlines
, "%d", header
.intnumlines
);
/* Attach a line count to the article. */
header
.intnumlines
= linecount
;
(void) sprintf(header
.numlines
, "%d", linecount
);
* Make the directory for a new newsgroup. ngname should be the
* full pathname of the directory. Do the other stuff too.
* The various games with setuid and chown are to try to make sure
* the directory is owned by NEWSUSR and NEWSGRP, which is tough to
* do if you aren't root. This will work on a UCB system (which allows
* setuid(geteuid()) or a USG system (which allows you to give away files
* you own with chown), otherwise you have to change your kernel to allow
* one of these things or run with your dirs 777 so that it doesn't matter
if (ngname
== NULL
|| !isalpha(ngname
[0]))
xerror("Tried to make illegal newsgroup %s", ngname
);
* If the parent is 755 the setuid(getuid)
* will fail, and since mkdir is suid, and our real uid is random,
* the mkdir will fail. So we have to temporarily chmod it to 777.
(void) strcpy(parent
, fulldir
);
while (p
= rindex(parent
, '/')) {
if (stat(parent
, &sbuf
) == 0) {
(void) chmod(parent
, 0777);
/* Create the directory */
if (mkdir(fulldir
, 0777) < 0)
xerror("Cannot mkdir %s: %s", fulldir
, errmsg(errno
));
(void) chmod(parent
, (int)sbuf
.st_mode
); /* put it back */
* Give away the directories we just created which were assigned
(void) chown(fulldir
, duid
, dgid
);
(void) strcpy(sysbuf
, fulldir
);
while (p
= rindex(sysbuf
, '/')) {
/* stop when get to last known good parent */
if (strcmp(sysbuf
, parent
) == 0)
(void) chown(sysbuf
, duid
, dgid
);
log("make newsgroup %s in dir %s", ngname
, fulldir
);
* If any parent directories of this dir don't exist, create them.
(void) strcpy(buf
, dname
);
if (mkdir(buf
, 0777) < 0)
xerror("Can not mkdir %s: %s", buf
, errmsg(errno
));
log("cancel article %s", filename
);
fp
= fopen(filename
, "r");
log("article %s not found", filename
);
if (hread(&header
, fp
, TRUE
) == NULL
)
xerror("Article is garbled.");