changeset 150:1c7303c71f66 noffle

[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.
author bears
date Thu, 26 Oct 2000 22:21:13 +0100
parents bfeea2bc09b6
children cb799054bd61
files ChangeLog NEWS docs/inews.1 docs/noffle.1 docs/noffle.conf.5 packages/redhat/noffle.spec src/configfile.c src/configfile.h src/noffle.c src/post.c src/post.h src/protocol.c src/protocol.h
diffstat 13 files changed, 599 insertions(+), 85 deletions(-) [+]
line wrap: on
line diff
--- 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>
 
--- 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:
 --------
--- /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.
--- 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),
--- 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
--- 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
--- 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
--- 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 */
--- 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 */
--- 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;
     
--- 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
--- 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;
+}
 
 
 
--- 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