# HG changeset patch
# User bears
# Date 972595273 -3600
# Node ID 1c7303c71f66b8dce32a0dde6907f37f7ba74dd3
# Parent  bfeea2bc09b678499711e28ecfbd21b46a4ed131
[svn]  * src/protocol.c: Fix bug in Prt_getLn if we should read a line
   starting with '\0' - according to the leafnode mailing list,
   this has been seen in the wild.
 * docs/inews.1,docs/noffle.1,docs/noffle.conf.5,
   packages/redhat/noffle.spec,src/configfile.h,src/configfile.c,
   src/noffle.c,src/post.h,src/post.c: Removed use of getopt_long,
   and added inews mode - the Noffle executable behaves
   as inews is invoked as inews. This includes adding From: and
   Organization: headers if necessary - add configs to override
   defaults for the From: domain and specify the organization.
   For all my fellow trn-heads out there, and users of any other
   ageing newsreader that expects inews. Updated RPM spec to create
   inews link to noffle on install.

diff -r bfeea2bc09b6 -r 1c7303c71f66 ChangeLog
--- a/ChangeLog	Thu Oct 26 22:13:28 2000 +0100
+++ b/ChangeLog	Thu Oct 26 22:21:13 2000 +0100
@@ -1,3 +1,22 @@
+Thu Oct 26 21:42:45 BST 2000 Jim Hague <jim.hague@acm.org>
+
+ * src/protocol.c: Fix bug in Prt_getLn if we should read a line
+   starting with '\0' - according to the leafnode mailing list,
+   this has been seen in the wild.
+ * docs/inews.1,docs/noffle.1,docs/noffle.conf.5,
+   packages/redhat/noffle.spec,src/configfile.h,src/configfile.c,
+   src/noffle.c,src/post.h,src/post.c: Removed use of getopt_long,
+   and added inews mode - the Noffle executable behaves
+   as inews is invoked as inews. This includes adding From: and
+   Organization: headers if necessary - add configs to override
+   defaults for the From: domain and specify the organization.
+   For all my fellow trn-heads out there, and users of any other
+   ageing newsreader that expects inews. Updated RPM spec to create 
+   inews link to noffle on install.
+ * src/server.c: When replying to a command, generate the reply into
+   a buffer, release the lock and then send the reply, so we don't
+   hog the lock should the reply stall for some network reason.
+
 Sun Oct 15 2000 Markus Enzenberger <markus.enzenberger@t-online.de>
 
  * acconfig.h,config.h.in,configure.in,src/Makefile.in,src/fetch.c:
@@ -21,6 +40,7 @@
  * src/post.c: do no longer always replace invalid message-IDs.
 
 Wed Aug 16 00:03:50 BST 2000 Jim Hague <jim.hague@acm.org>
+
  * Permit 'k' and 'm' suffices after numbers in filter rules.
  
 Fri Aug 11 2000 Markus Enzenberger <markus.enzenberger@t-online.de>
@@ -78,6 +98,9 @@
    data first and then process. The aim is to ensure the lock isn't held
    with a network operation is in progress. I may carry on and extend this
    to all server operations as well (so a slow client can't hog the lock).
+ * docs/noffle.conf.5,src/configfile.h,src/configfile.c,src/post.c,
+   src/protocol.h,src/protocol.c: Add Path: header to newly posted
+   articles. Provide default content - path-header in config overrides.
 	
 Sat Jul 22 2000 Markus Enzenberger <markus.enzenberger@t-online.de>
 
diff -r bfeea2bc09b6 -r 1c7303c71f66 NEWS
--- a/NEWS	Thu Oct 26 22:13:28 2000 +0100
+++ b/NEWS	Thu Oct 26 22:21:13 2000 +0100
@@ -5,6 +5,9 @@
  * Article filtering: see noffle.conf(5) for details.
  * Noffle no longer hangs if the connection breaks down during a fetch.
  * SENDMAILPROG is used instead of mail for returning failed postings.
+ * Noffle will act as inews is invoked as inews.
+ * Fetching and reading news can overlap, and generally blocking on the
+   database lock reduced to a minimum.
 
 1.0pre7:
 --------
diff -r bfeea2bc09b6 -r 1c7303c71f66 docs/inews.1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/inews.1	Thu Oct 26 22:21:13 2000 +0100
@@ -0,0 +1,122 @@
+.TH inews 1
+.\" $Id: inews.1 227 2000-10-26 21:21:13Z bears $
+.SH NAME
+inews \- Send a Usenet article.
+
+.SH SYNOPSIS
+
+.B inews
+[
+.B \-D
+]
+[
+.B \-O
+]
+[
+.B \-S
+]
+[
+.I input
+]
+
+.SH DESCRIPTION
+
+.I inews
+reads a Usenet news article including headers from
+the named file or standard input if no file is given.
+It adds some headers and performs some checks. If the article
+fails the checks (e.g. the article lacks a body, or has no Newsgroups:
+line), the article is rejected. Otherwise the article is posted.
+
+
+.SH OPTIONS
+
+.TP
+.B \-D
+Debug mode.
+.I inews
+adds header lines and performs some checks. If the checks are successful,
+the article is printed on the standard output and not posted.
+.TP
+.B \-O
+If no
+.I Organization:
+header is found, and a value for
+.I organization
+is specified in
+.BR noffle.conf (5),
+.I inews
+will add a
+.I Organization:
+header using that value. To suppress this behaviour, use the
+.B \-O
+flag.
+.TP
+.B "\-h \-A \-V \-W"
+For compatibility with inews programs used with other news servers,
+.I inews
+accepts, but ignores, the ``\fB\-h\fP'', ``\fB\-A\fP'', ``\fB\-V\fP''
+and ``\fB\-W\fP'' flags.
+.TP
+.B \-N
+For compatability with inews programs used with other news servers,
+the ``\fB\-N\fP'' flag is treated as the ``\fB\-D\fP'' flag.
+.TP
+.B \-S
+Normally, if a file named
+.I .signature
+exists in the user's home directory,
+.I inews
+will try to append it to the end of the article. If the file cannot be
+read, the article will not be posted.
+If the
+.B \-S
+flag is given, .signature will not be added.
+
+.SH NOTES
+
+.I inews
+exits with a zero status if the article was successfully posted, or a
+non-zero status if a check fails or posting fails.
+
+.PP
+This
+.I inews
+is supplied with the
+.B NOFFLE
+news server. It is, in fact, the main
+.BR noffle (1)
+executable, and the
+.I inews
+program is just a link to the main
+.I noffle
+program. If the
+.I noffle
+program sees it is being executed under the name
+.I inews
+it adjusts its behaviour accordingly.
+
+.SH FILES
+
+.B NOFFLE
+takes its configuration from a configuration file, by default
+.I /etc/noffle.conf.
+For a description of this file, see
+.BR noffle.conf(5).
+
+.SH SEE ALSO
+
+.BR noffle (1),
+.BR noffle.conf (5),
+
+.SH AUTHORS
+
+Markus Enzenberger <markus.enzenberger@t-online.de>
+.br
+Volker Wysk <volker.wysk@student.uni-tuebingen.de>
+.br
+Jim Hague <jim.hague@acm.org>
+.br
+Uwe Hermann <uh1763@bingo-ev.de>
+
+1998-2000.
diff -r bfeea2bc09b6 -r 1c7303c71f66 docs/noffle.1
--- a/docs/noffle.1	Thu Oct 26 22:13:28 2000 +0100
+++ b/docs/noffle.1	Thu Oct 26 22:21:13 2000 +0100
@@ -1,5 +1,5 @@
 .TH noffle 1
-.\" $Id: noffle.1 183 2000-07-25 12:14:54Z bears $
+.\" $Id: noffle.1 227 2000-10-26 21:21:13Z bears $
 .SH NAME
 noffle \- Usenet package optimized for dialup connections.
 
@@ -289,6 +289,15 @@
 Remove group with name <group> from list of groups that are presently to
 be fetched.
 
+.SH NOTES
+
+If the
+.I noffle
+prgram sees the name under which it is executed is
+.IR inews ,
+it changes its behaviour to that described in
+.BR inews (1).
+
 .SH FILES
 
 .B NOFFLE
@@ -338,6 +347,7 @@
 
 .SH SEE ALSO
 
+.BR inews (1),
 .BR noffle.conf (5),
 .BR crond (8),
 .BR inetd (8),
diff -r bfeea2bc09b6 -r 1c7303c71f66 docs/noffle.conf.5
--- a/docs/noffle.conf.5	Thu Oct 26 22:13:28 2000 +0100
+++ b/docs/noffle.conf.5	Thu Oct 26 22:21:13 2000 +0100
@@ -1,5 +1,5 @@
 .TH noffle.conf 5
-.\" $Id: noffle.conf.5 199 2000-08-15 23:08:13Z bears $
+.\" $Id: noffle.conf.5 227 2000-10-26 21:21:13Z bears $
 
 .SH NAME
 noffle.conf \- Configuration file for NOFFLE news server
@@ -77,6 +77,30 @@
 Default: 300
 
 .TP
+.B from-domain <domain>
+When invoked as
+.BR inews (1)
+.B NOFFLE
+will add a From: line to posts that lack one. The address given
+uses the userid of the user who invoked 
+.B inews
+and the specified domain.
+.br
+Default: The system domain
+
+.TP
+.B organization <organization>
+When invoked as
+.BR inews (1)
+.B NOFFLE
+will, unless the -O flag is specified, add an Organization: line to
+posts that lack one. If no
+.B organization
+is specified, the line is not added.
+.br
+Default: <empty string>
+
+.TP
 .B mail-to <address>
 Receiver of failed postings. If empty then failed postings are returned
 to the sender (taking the address from the article's Sender, X-Sender or
@@ -177,7 +201,7 @@
 has its default value (empty) the header content
 is "<hostname>!not-for-mail".
 Use the
-.B default-path
+.B path-header
 setting to provide alternate content for the Path: header.
 This will very rarely be necessary.
 .br
diff -r bfeea2bc09b6 -r 1c7303c71f66 packages/redhat/noffle.spec
--- a/packages/redhat/noffle.spec	Thu Oct 26 22:13:28 2000 +0100
+++ b/packages/redhat/noffle.spec	Thu Oct 26 22:21:13 2000 +0100
@@ -12,7 +12,6 @@
 
 Requires:	gdbm
 Requires:	mailx
-Requires:	textutils
 
 %description
 NOFFLE is a news server optimized for low speed dialup connection to the
@@ -47,6 +46,8 @@
 install -m 0755 -o news -g news packages/redhat/noffle-expire $RPM_BUILD_ROOT/etc/cron.daily/noffle-expire
 
 %post
+# Create inews line
+ln -sf $RPM_BUILD_ROOT/%{prefix}/bin/noffle $RPM_BUILD_ROOT/%{prefix}/bin/inews
 # Update /etc/inetd.conf
 if ! grep -q '^[# \t]*nntp' /etc/inetd.conf ; then
 	echo >> /etc/inetd.conf
@@ -66,6 +67,7 @@
 %postun
 # The script gets 0 on uninstall, 1 on upgrade. Don't remove
 # hosts.deny/inetd.conf lines on upgrade.
+rm $RPM_BUILD_ROOT/%{prefix}/bin/inews
 if [ "$1" = 0 ] ; then
 	if [ -f /etc/inetd.conf ] &&
 	   grep noffle /etc/inetd.conf > /dev/null 2>&1 ; then
@@ -93,6 +95,7 @@
 %{prefix}/bin/noffle
 %{prefix}/man/man1/noffle.1
 %{prefix}/man/man5/noffle.conf.5
+%{prefix}/bin/inews
 /etc/cron.daily/noffle-expire
 %dir /var/spool/noffle
 %dir /var/spool/noffle/data
@@ -102,6 +105,9 @@
 %dir /var/spool/noffle/overview
 
 %changelog
+* Thu Oct 26 2000 Jim Hague <jim.hague@am.org>
+- Added inews link.
+
 * Sun Jun 18 2000 Jim Hague <jim.hague@am.org>
 - Version 1.0pre6-3 RPM
 - Changed /etc/noffle.conf mode to 0600 in case server password is required
diff -r bfeea2bc09b6 -r 1c7303c71f66 src/configfile.c
--- a/src/configfile.c	Thu Oct 26 22:13:28 2000 +0100
+++ b/src/configfile.c	Thu Oct 26 22:21:13 2000 +0100
@@ -6,7 +6,7 @@
     SPOOLDIR
     VERSION
 
-  $Id: configfile.c 199 2000-08-15 23:08:13Z bears $
+  $Id: configfile.c 227 2000-10-26 21:21:13Z bears $
 */
 
 #if HAVE_CONFIG_H
@@ -92,6 +92,8 @@
     int maxAutoSubscribeMode;
     AutoSubscribeModeEntry *autoSubscribeMode;
     Str pathHeader;
+    Str fromDomain;
+    Str organization;
 } config =
 {
     SPOOLDIR, /* spoolDir */
@@ -118,7 +120,9 @@
     0,        /* numAutoSubscribeMode */
     0,        /* maxAutoSubscribeMode */
     NULL,     /* autoSubscribeMode */
-    ""        /* pathHeader */
+    "",       /* pathHeader */
+    "",       /* fromDomain */
+    ""        /* organization */
 };
 
 const char * Cfg_spoolDir( void ) { return config.spoolDir; }
@@ -138,6 +142,8 @@
 const char * Cfg_mailTo( void ) { return config.mailTo; }
 int Cfg_defaultExpire( void ) { return config.defaultExpire; }
 const char * Cfg_pathHeader( void ) { return config.pathHeader; }
+const char * Cfg_fromDomain( void ) { return config.fromDomain; }
+const char * Cfg_organization( void ) { return config.organization; }
 
 void
 Cfg_beginServEnum( void )
@@ -363,6 +369,16 @@
 }
 
 static void
+getText( Str variable, const char *line )
+{
+    const char *l;
+    
+    /* Skip command */
+    l = Utl_restOfLn( line, 1 );
+    Utl_cpyStr( variable, l );
+}
+
+static void
 getServ( const char *line )
 {
     Str dummy, name, user, pass;
@@ -808,11 +824,15 @@
             getGroups( p, TRUE );
         else if ( strcmp( "omitgroups", name ) == 0 )
             getGroups( p, FALSE );
+        else if ( strcmp( "path-header", name ) == 0 )
+            getStr( config.pathHeader, p );
+        else if ( strcmp( "from-domain", name ) == 0 )
+            getStr( config.fromDomain, p );
 	/* The following need line because they may have uppercase data */
+        else if ( strcmp( "organization", name ) == 0 )
+            getText( config.organization, line );
         else if ( strcmp( "server", name ) == 0 )
             getServ( line );
-        else if ( strcmp( "path-header", name ) == 0 )
-            getStr( config.pathHeader, p );
 	else if ( strcmp( "filter", name ) == 0 )
 	    getFilter( line );
         else
diff -r bfeea2bc09b6 -r 1c7303c71f66 src/configfile.h
--- a/src/configfile.h	Thu Oct 26 22:13:28 2000 +0100
+++ b/src/configfile.h	Thu Oct 26 22:21:13 2000 +0100
@@ -3,7 +3,7 @@
 
   Common declarations and handling of the configuration file.
 
-  $Id: configfile.h 155 2000-06-24 20:28:01Z bears $
+  $Id: configfile.h 227 2000-10-26 21:21:13Z bears $
 */
 
 #ifndef CONFIGFILE_H
@@ -61,6 +61,12 @@
 /* Return Path: header contents */
 const char * Cfg_pathHeader( void );
 
+/* Return domain for generated From: lines */
+const char * Cfg_fromDomain( void );
+
+/* Return default Organization: contents */
+const char * Cfg_organization( void );
+
 void Cfg_read( void );
 
 /* Get a new iterator for get group patterns for the given server */
diff -r bfeea2bc09b6 -r 1c7303c71f66 src/noffle.c
--- a/src/noffle.c	Thu Oct 26 22:13:28 2000 +0100
+++ b/src/noffle.c	Thu Oct 26 22:21:13 2000 +0100
@@ -10,7 +10,7 @@
   received for some seconds (to allow multiple clients connect at the same
   time).
 
-  $Id: noffle.c 197 2000-08-14 19:49:56Z bears $
+  $Id: noffle.c 227 2000-10-26 21:21:13Z bears $
 */
 
 #if HAVE_CONFIG_H
@@ -19,7 +19,6 @@
 
 #include <ctype.h>
 #include <errno.h>
-#include <getopt.h>
 #include <signal.h>
 #include <sys/time.h>
 #include <sys/resource.h>
@@ -182,18 +181,18 @@
 }
 
 static Bool
-doPost( void )
+doPost( FILE *f, unsigned flags )
 {
     Str line;
     DynStr *s;
     Bool res;
 
     s = new_DynStr( 10000 );
-    while ( fgets( line, MAXCHAR, stdin ) != NULL )
+    while ( fgets( line, MAXCHAR, f ) != NULL )
 	DynStr_app( s, line );
 
     res = TRUE;
-    if ( ! Post_open( DynStr_str( s ) ) )
+    if ( ! Post_open( DynStr_str( s ), flags ) )
     {
 	fprintf( stderr, "Post failed: Malformed article.\n" );
 	res = FALSE;
@@ -640,36 +639,144 @@
     raise( sig );
 }
 
+static void
+printInewsUsage( void )
+{
+    static const char *msg =
+	"Usage: inews [-D] [-O] [-S] [input]\n"
+	" -D    Debug - send article to stdout and don't post\n"
+	" -O    Don't add Organization header\n"
+	" -S    Don't add .signature\n"
+	" input File containing message, standard input if none specified.\n"
+	"For compatability, -h, -A, -V, -W are ignored and -N is\n"
+	"equivalent to -D.\n";
+    fprintf( stderr, "%s", msg );
+}
+
+static int
+doInews( int argc, char **argv )
+{
+    int result;
+    int flags;
+    FILE *f;
+
+    UNUSED( argc );
+    
+    noffle.lockAtStartup = TRUE;
+
+    /* Process options */
+    flags = POST_ADD_ORG | POST_ADD_SIG | POST_ADD_FROM;
+    for ( ; argv[0] != NULL && argv[0][0] == '-' ; argv++ )
+    {
+	if ( argv[0][2] != '\0' )
+	{
+	    printInewsUsage();
+	    return EXIT_FAILURE;
+	}
+	
+	switch( argv[0][1] )
+	{
+	case 'h':
+	case 'A':
+	case 'V':
+	case 'W':
+	    break;
+	case 'N':
+	case 'D':
+	    flags |= POST_DEBUG;
+	    break;
+	case 'O':
+	    flags &= ~POST_ADD_ORG;
+	    break;
+	case 'S':
+	    flags &= ~POST_ADD_SIG;
+	    break;
+	default:
+	    printInewsUsage();
+	    return EXIT_FAILURE;
+	}
+    }
+    
+    if ( argv[0] ==  NULL )
+	f = stdin;
+    else
+    {
+	f = fopen( argv[0], "r" );
+	if ( f == NULL )
+	{
+	    Log_err( "Can't access %s (%s).", argv[0], strerror( errno ) );
+	    return EXIT_FAILURE;
+	}
+    }
+
+    if ( ! initNoffle() )
+	return EXIT_FAILURE;
+    result = EXIT_SUCCESS;
+
+    if ( ! doPost( f, flags ) )
+	result = EXIT_FAILURE;
+
+    if ( f != stdin )
+	fclose( f );
+
+    closeNoffle();
+    return result;
+}
+
+static int
+getArgLetter(const char *arg)
+{
+    int res;
+    struct option
+    {
+	const char *longOpt;
+	const char *opt;
+    } options[] =
+    {
+	{ "--article", 		"-a" },
+	{ "--cancel", 		"-c" },
+	{ "--create", 		"-C" },
+	{ "--database",		"-d" },
+	{ "--delete", 		"-D" },
+	{ "--expire", 		"-e" },
+	{ "--fetch", 		"-f" },
+	{ "--groups", 		"-g" },
+	{ "--help", 		"-h" },
+	{ "--list", 		"-l" },
+	{ "--modify", 		"-m" },
+	{ "--offline", 		"-o" },
+	{ "--online", 		"-n" },
+	{ "--post", 		"-p" },
+	{ "--query", 		"-q" },
+	{ "--server",		"-r" },
+	{ "--requested",	"-R" },
+	{ "--subscribe-over",	"-s" },
+	{ "--subscribe-full",	"-S" },
+	{ "--subscribe-thread",	"-t" },
+	{ "--unsubscribe",	"-u" },
+	{ "--version",		"-v" },
+	{ NULL,			NULL }
+	
+    };
+    struct option *opt;
+
+    res = -1;
+    for ( opt = options; opt->opt != NULL; opt++ )
+	if ( strcmp( arg, opt->longOpt ) == 0 ||
+	     strcmp( arg, opt->opt ) == 0 )
+	{
+	    res = opt->opt[1];
+	    break;
+	}
+
+    return res;
+}
+
 int main ( int argc, char **argv )
 {
     int c, result;
-    struct option longOptions[] =
-    {
-        { "article",          required_argument, NULL, 'a' },
-        { "cancel",           required_argument, NULL, 'c' },
-        { "create",           required_argument, NULL, 'C' },
-        { "database",         no_argument,       NULL, 'd' },
-        { "delete",           required_argument, NULL, 'D' },
-        { "expire",           no_argument,       NULL, 'e' },
-        { "fetch",            no_argument,       NULL, 'f' },
-        { "groups",           no_argument,       NULL, 'g' },
-        { "help",             no_argument,       NULL, 'h' },
-        { "list",             no_argument,       NULL, 'l' },
-        { "modify",           required_argument, NULL, 'm' },
-        { "offline",          no_argument,       NULL, 'o' },
-        { "online",           no_argument,       NULL, 'n' },
-        { "post",             no_argument, 	 NULL, 'p' },
-        { "query",            required_argument, NULL, 'q' },
-        { "server",           no_argument,       NULL, 'r' },
-        { "requested",        no_argument,       NULL, 'R' },
-        { "subscribe-over",   required_argument, NULL, 's' },
-        { "subscribe-full",   required_argument, NULL, 'S' },
-        { "subscribe-thread", required_argument, NULL, 't' },
-        { "unsubscribe",      required_argument, NULL, 'u' },
-        { "version",          no_argument,       NULL, 'v' },
-        { NULL, 0, NULL, 0 }
-    };
-    
+    const char *cmdname, *p;
+
     signal( SIGSEGV, bugReport );
     signal( SIGABRT, logSignal );
     signal( SIGFPE, logSignal );
@@ -677,55 +784,72 @@
     signal( SIGINT, logSignal );
     signal( SIGTERM, logSignal );
     signal( SIGPIPE, logSignal );
-    c = getopt_long( argc, argv, "a:c:C:dD:efghlm:onpq:rRs:S:t:u:v",
-                     longOptions, NULL );
+
+    /* Find last component of command name. */
+    cmdname = argv[0];
+    p = strrchr( cmdname, '/' );
+    if ( p != NULL )
+	cmdname = ++p;
+
+    argv++;
+    argc--;
+
+    /* Were we invoked as inews? */
+    if ( strcmp( cmdname, "inews" ) == 0 )
+	return doInews( argc, argv );
+
+    c = -1;
+    if ( *argv != NULL )
+    {
+	c = getArgLetter( *argv );
+	argv++;
+	argc--;
+    }
+
     noffle.lockAtStartup = ! ( c == 'r' || c == 'h' );
     if ( ! initNoffle() )
         return EXIT_FAILURE;
     result = EXIT_SUCCESS;
     switch ( c )
     {
-    case 0:
-        /* Options that set a flag. */
-        break;
     case 'a':
-        if ( ! optarg )
+        if ( *argv == NULL )
         {
             fprintf( stderr, "Option -a needs argument.\n" );
             result = EXIT_FAILURE;
         }
         else
-            doArt( optarg );
+            doArt( *argv );
         break;
     case 'c':
-        if ( ! optarg )
+        if ( *argv == NULL )
         {
             fprintf( stderr, "Option -c needs argument.\n" );
             result = EXIT_FAILURE;
         }
         else
-            doCancel( optarg );
+            doCancel( *argv );
         break;
     case 'C':
-        if ( ! optarg )
+        if ( *argv == NULL  )
         {
             fprintf( stderr, "Option -C needs argument.\n" );
             result = EXIT_FAILURE;
         }
         else
-            doCreateLocalGroup( optarg );
+            doCreateLocalGroup( *argv );
         break;
     case 'd':
         doDb();
         break;
     case 'D':
-        if ( ! optarg )
+        if ( *argv == NULL )
         {
             fprintf( stderr, "Option -D needs argument.\n" );
             result = EXIT_FAILURE;
         }
         else
-            doDeleteLocalGroup( optarg );
+            doDeleteLocalGroup( *argv );
         break;
     case 'e':
 	doExpire();
@@ -744,13 +868,13 @@
         doList();
         break;
     case 'm':
-        if ( ! optarg )
+        if ( *argv == NULL )
         {
             fprintf( stderr, "Option -m needs argument.\n" );
             result = EXIT_FAILURE;
         }
         else
-	    if ( ! doModify( optarg, argc - optind, &argv[ optind ] ) )
+	    if ( ! doModify( *argv, --argc, ++argv ) )
 		result = EXIT_FAILURE;
         break;
     case 'n':
@@ -766,24 +890,24 @@
             Online_set( FALSE );
         break;
     case 'p':
-	if ( ! doPost() )
+	if ( ! doPost( stdin, 0 ) )
 	    result = EXIT_FAILURE;
         break;
     case 'q':
-        if ( ! optarg )
+        if ( *argv == NULL )
         {
             fprintf( stderr, "Option -q needs argument.\n" );
             result = EXIT_FAILURE;
         }
         else
         {
-            if ( strcmp( optarg, "groups" ) == 0 )
+            if ( strcmp( *argv, "groups" ) == 0 )
                 noffle.queryGrps = TRUE;
-            else if ( strcmp( optarg, "desc" ) == 0 )
+            else if ( strcmp( *argv, "desc" ) == 0 )
                 noffle.queryDsc = TRUE;
             else
             {
-                fprintf( stderr, "Unknown argument -q %s\n", optarg );
+                fprintf( stderr, "Unknown argument -q %s\n", *argv );
                 result = EXIT_FAILURE;
             }
             doQuery();
@@ -794,43 +918,43 @@
         Server_run();
         break;
     case 'R':
-        doRequested( optarg );
+        doRequested( *argv );
         break;
     case 's':
-        if ( ! optarg )
+        if ( *argv == NULL )
         {
             fprintf( stderr, "Option -s needs argument.\n" );
             result = EXIT_FAILURE;
         }
         else
-            result = doSubscribe( optarg, OVER );
+            result = doSubscribe( *argv, OVER );
         break;
     case 'S':
-        if ( ! optarg )
+        if ( *argv == NULL )
         {
             fprintf( stderr, "Option -S needs argument.\n" );
             result = EXIT_FAILURE;
         }
         else
-            doSubscribe( optarg, FULL );
+            doSubscribe( *argv, FULL );
         break;
     case 't':
-        if ( ! optarg )
+        if ( *argv == NULL )
         {
             fprintf( stderr, "Option -t needs argument.\n" );
             result = EXIT_FAILURE;
         }
         else
-            result = doSubscribe( optarg, THREAD );
+            result = doSubscribe( *argv, THREAD );
         break;
     case 'u':
-        if ( ! optarg )
+        if ( *argv == NULL )
         {
             fprintf( stderr, "Option -u needs argument.\n" );
             result = EXIT_FAILURE;
         }
         else
-            doUnsubscribe( optarg );
+            doUnsubscribe( *argv );
         break;
     case '?':
         /* Error message already printed by getopt_long */
diff -r bfeea2bc09b6 -r 1c7303c71f66 src/post.c
--- a/src/post.c	Thu Oct 26 22:13:28 2000 +0100
+++ b/src/post.c	Thu Oct 26 22:21:13 2000 +0100
@@ -1,14 +1,18 @@
 /*
   post.c
 
-  $Id: post.c 202 2000-08-23 09:52:25Z enz $
+  $Id: post.c 227 2000-10-26 21:21:13Z bears $
 */
 
 #if HAVE_CONFIG_H
 #include <config.h>
 #endif
 
+#include <errno.h>
+#include <pwd.h>
 #include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
 #include "post.h"
 #include <string.h>
 #include "common.h"
@@ -25,6 +29,9 @@
 #include "util.h"
 #include "portable.h"
 
+#define	BEGIN_SIG	"-- "
+#define	SIG_FILE	"/.signature"
+
 struct OverInfo
 {
     Str subject;
@@ -43,10 +50,11 @@
     ItemList *control;	   /* Control message? NULL if not */
     Bool approved;	   /* Has Approved: header? */
     Bool posted;	   /* Has it been put in the article database? */
+    int flags;             /* Posting flags */
     struct OverInfo over;
 };
 
-static struct Article article = { NULL, NULL, NULL, FALSE, FALSE,
+static struct Article article = { NULL, NULL, NULL, FALSE, FALSE, 0,
 				  { "", "", "", "", "", 0, 0 } };
 
 /* Add the article to a group. */
@@ -163,14 +171,15 @@
 {
     DynStr * s;
     Str line, field, value;
-    Bool replyToFound, pathFound;
+    Bool replyToFound, pathFound, orgFound;
     time_t t;
+    int sigLines;
 
     s = new_DynStr( 10000 );
     article.text = s;
 
     memset( &article.over, 0, sizeof( article.over ) );
-    replyToFound = pathFound = FALSE;
+    replyToFound = pathFound = orgFound = FALSE;
     
     /* Grab header lines first, getting overview info as we go. */
     while ( ( p = Utl_getHeaderLn( line, p ) ) != NULL
@@ -222,6 +231,11 @@
 	    pathFound = TRUE;
 	    DynStr_appLn( s, line );
 	}
+	else if ( strcmp ( field, "organization" ) == 0 )
+	{
+	    orgFound = TRUE;
+	    DynStr_appLn( s, line );
+	}
 	else if ( strcmp ( field, "x-sender" ) == 0 )
 	{
 	    DynStr_app( s, "X-NOFFLE-X-Sender: " );
@@ -236,8 +250,22 @@
     /* Now sort header-related issues */
     if ( article.over.from[ 0 ] == '\0' )
     {
-	Log_err( "Posted message has no From field" );
-	return FALSE;
+	if ( article.flags & POST_ADD_FROM )
+	{
+	    Log_dbg( "Adding From field to posted message." );
+	    DynStr_app( s, "From: " );
+	    if ( ! Prt_genFromHdr( article.over.from ) )
+	    {
+		Log_err( "Can't generate From field" );
+		return FALSE;
+	    }
+	    DynStr_appLn( s, article.over.from );
+	}
+	else
+	{
+	    Log_err( "Posted message has no From field" );
+	    return FALSE;
+	}
     }
     if ( article.over.subject[ 0 ] == '\0' )
     {
@@ -295,6 +323,20 @@
 	DynStr_appLn( s, article.over.from );
     }
 
+    /* Ensure Organization header if required */
+    if ( ( ! orgFound ) && ( article.flags & POST_ADD_ORG ) )
+    {
+	Str org;
+
+	Utl_cpyStr( org, Cfg_organization() );
+	if ( org[ 0 ] != '\0' )
+	{
+	    Log_dbg( "Adding Organization field to posted message." );
+	    DynStr_app( s, "Organization: " );
+ 	    DynStr_appLn( s, org );
+	}
+    }
+
     /* OK, header ready to roll. Something to accompany it? */
     if ( p == NULL || p[ 0 ] == '\0' )
     {
@@ -305,9 +347,68 @@
     /* Add the empty line separating header and body */
     DynStr_appLn( s, "" );
 
-    /* Now pop on the rest of the body and count the lines & bytes */
+    /* Now pop on the rest of the body */
     DynStr_app( s, p );
-    for ( p++, article.over.lines = 0; *p != '\0'; p++ )
+
+    /* Add a signature if requested to do so and if one found. */
+    sigLines = 0;
+    if ( article.flags & POST_ADD_SIG )
+    {
+	Str sigfile;
+	struct passwd *pwd;
+	FILE *f;
+
+	/* Generate sig file path */
+	pwd = getpwuid( getuid() );
+	Utl_cpyStr( sigfile, pwd->pw_dir );
+	Utl_catStr( sigfile, SIG_FILE );
+
+	f = fopen( sigfile, "r" );
+	if ( f == NULL )
+	{
+	    /* If err is ENOENT, file doesn't exist. This is OK. */
+	    if ( errno != ENOENT )
+	    {
+		Log_err( "Can't access .signature file (%s), "
+			 "article not posted.",
+			 strerror( errno ) );
+		return FALSE;
+	    }
+	}
+	else
+	{
+	    /* OK, try to add it. */
+	    Str sline;
+	    
+	    Log_dbg( "Adding .signature to posted message." );
+
+	    DynStr_appLn( s, BEGIN_SIG );
+	    sigLines++;
+	    while ( Prt_getLn( sline, f, 0 ) )
+	    {
+		DynStr_appLn( s, sline );
+		sigLines++;
+	    }
+
+	    if ( ferror( f ) )
+	    {
+		Log_err( "Error reading .signature file (%s), "
+			 "article not posted.",
+			 strerror( errno ) );
+		fclose( f );
+		return FALSE;
+	    }
+
+	    fclose( f );
+	}
+    }
+
+    /*
+     * Count the lines & bytes. This counts the original number of
+     * lines in the supplied body, so add in the number of signature
+     * lines added, including the separator.
+     */
+    for ( p++, article.over.lines = sigLines; *p != '\0'; p++ )
 	if ( *p == '\n' )
 	    article.over.lines++;
     article.over.bytes = DynStr_len( s );
@@ -428,7 +529,7 @@
 
 /* Register an article for posting. */
 Bool
-Post_open( const char * text )
+Post_open( const char * text, unsigned flags )
 {
     if ( article.text != NULL )
     {
@@ -436,6 +537,8 @@
 	return FALSE;
     }
 
+    article.flags = flags;
+    
     if ( ! getArticleText( text ) )
 	return FALSE;
 
@@ -453,6 +556,12 @@
 Bool
 Post_post( void )
 {
+    if ( article.flags & POST_DEBUG )
+    {
+	fputs( DynStr_str( article.text ), stdout );
+	return TRUE;
+    }
+
     if ( ! checkPostableNewsgroup() )
 	return FALSE;
     
diff -r bfeea2bc09b6 -r 1c7303c71f66 src/post.h
--- a/src/post.h	Thu Oct 26 22:13:28 2000 +0100
+++ b/src/post.h	Thu Oct 26 22:21:13 2000 +0100
@@ -5,7 +5,7 @@
   necessary, and add to the local database and/or queue for external
   posting as appropriate.
 
-  $Id: post.h 159 2000-06-24 20:47:40Z bears $
+  $Id: post.h 227 2000-10-26 21:21:13Z bears $
 */
 
 #ifndef POST_H
@@ -17,9 +17,15 @@
 
 #include "common.h"
 
+/* Flags for Post_open */
+#define	POST_ADD_ORG		0001
+#define	POST_DEBUG		0002
+#define	POST_ADD_SIG		0004
+#define	POST_ADD_FROM		0010
+
 /* Register an article for posting. */
 Bool
-Post_open( const char * text );
+Post_open( const char * text, unsigned flags );
 
 /* Post the article. */
 Bool
diff -r bfeea2bc09b6 -r 1c7303c71f66 src/protocol.c
--- a/src/protocol.c	Thu Oct 26 22:13:28 2000 +0100
+++ b/src/protocol.c	Thu Oct 26 22:21:13 2000 +0100
@@ -1,7 +1,7 @@
 /*
   protocol.c
 
-  $Id: protocol.c 217 2000-09-23 10:40:35Z enz $
+  $Id: protocol.c 227 2000-10-26 21:21:13Z bears $
 */
 
 #if HAVE_CONFIG_H
@@ -11,6 +11,7 @@
 #include <stdio.h> 
 #include <ctype.h> 
 #include <netdb.h>
+#include <pwd.h>
 #include <signal.h>
 #include <sys/types.h>
 #include <sys/utsname.h>
@@ -79,10 +80,10 @@
     if ( ret == NULL )
         return FALSE;
     len = strlen( line );
-    if ( line[ len - 1 ] == '\n' )
+    if ( len > 0 && line[ len - 1 ] == '\n' )
     {
         line[ len - 1 ] = '\0';
-        if ( line[ len - 2 ] == '\r' )
+        if ( len > 1 && line[ len - 2 ] == '\r' )
             line[ len - 2 ] = '\0';
     }
     Log_dbg( "[R] %s", line );
@@ -344,6 +345,63 @@
     Utl_catStr( pathHdr, "!not-for-mail" );
 }
 
+Bool
+Prt_genFromHdr( Str fromHdr )
+{
+    Str name, domain;
+    const char *nameval;
+    struct passwd *pwd;
+
+    /* First get the domain to use. If config empty, use FQDN */
+    Utl_cpyStr( domain, Cfg_fromDomain() );
+
+    if ( DynStr_len( domain ) == 0 )
+	if ( ! getFQDN( domain ) )
+	    Utl_catStr( domain, "unknown" );
+
+    /* Now get pwd for the username */
+    pwd = getpwuid( getuid() );
+    if ( pwd == NULL )
+	return FALSE;
+
+    /* Now for their name - use env NAME if available */
+    nameval = getenv( "NAME" );
+    if ( nameval != NULL )
+	Utl_cpyStr( name, nameval );
+    else
+    {
+	char *p;
+	
+	/*
+	  Extract from GECOS field. Following the lead of the INN inews,
+	  ignore leading stuff like "23-" "stuff]-" or "stuff -" as well
+	  as trailing whitespace, or anything that comes after
+	  a comma or semicolon.
+	 */
+	nameval = pwd->pw_gecos;
+	p = strchr( nameval, '-' );
+	if ( p != NULL && p > nameval &&
+	     ( p[-1] == ']' || p[-1] == ' ' || isdigit( p[ -1 ] ) ) )
+	    nameval = p;
+	p = strrchr( nameval, ',' );
+	if ( p != NULL )
+	    *p = '\0';
+	p = strchr( nameval, ';' );
+	if ( p != NULL )
+	    *p = '\0';
+	Utl_cpyStr( name, nameval );
+    }
+
+    /* OK, build From: contents */
+    Utl_cpyStr( fromHdr, pwd->pw_name );
+    Utl_catStr( fromHdr, "@" );
+    Utl_catStr( fromHdr, domain );
+    Utl_catStr( fromHdr, " (" );
+    Utl_catStr( fromHdr, name );
+    Utl_catStr( fromHdr, ")" );
+
+    return TRUE;
+}
 
 
 
diff -r bfeea2bc09b6 -r 1c7303c71f66 src/protocol.h
--- a/src/protocol.h	Thu Oct 26 22:13:28 2000 +0100
+++ b/src/protocol.h	Thu Oct 26 22:21:13 2000 +0100
@@ -4,7 +4,7 @@
   Functions related with the NNTP protocol which are useful for both
   the server and the client.
 
-  $Id: protocol.h 217 2000-09-23 10:40:35Z enz $
+  $Id: protocol.h 227 2000-10-26 21:21:13Z bears $
 */
 
 #ifndef PRT_H
@@ -111,4 +111,7 @@
 void
 Prt_genPathHdr( Str pathHdr, const char *from );
 
+Bool
+Prt_genFromHdr( Str fromHdr );
+
 #endif