Mercurial > noffle
view src/post.c @ 288:c02c4eb95f95 noffle
[svn] * src/configfile.h,src/configfile.c,docs/noffle.conf.5: Add noffle-user
and noffle-group configs.
* src/configfile.c,src/fetch.c,src/fetchlist.c,src/protocol.c,
src/server.c: Replace strcpy() with Utl_cpyStr() where appropriate.
See Debian bug 168128.
* src/control.c,src/configfile.c,src/noffle.c: Replace [s]scanf("%s")
with [s]scanf(MAXCHAR_FMT).
* src/noffle.c: Log warning if noffle.conf is world readable.
* src/noffle.c: Restrict most options to news admins; i.e. those who
are root or news on running Noffle.
* Makefile.in,acconfig.h,aclocal.m4,config.h.in,configure,configure.in,
docs/Makefile.in,docs/noffle.conf.5,packages/Makefile.in,
packages/redhat/Makefile.in,src/Makefile.am,src/Makefile.in,
src/authenticate.c,src/authenticate.h,src/noffle.c,src/server.c:
Add basic authentication using either Noffle-specific user file
or authenticating via PAM (service 'noffle'). PAM authentication
needs to run as root, so a Noffle server that needs PAM
must be started by root. Helpful (?) error messages will be logged
if not. Noffle will switch ruid and euid to 'news' (or whatever
is configured) ASAP.
* src/noffle.c: Add uid checking.
author | bears |
---|---|
date | Fri, 10 Jan 2003 23:25:45 +0000 |
parents | baa6408d1bbc |
children | b0ee77fa24d4 |
line wrap: on
line source
/* post.c $Id: post.c 414 2003-01-06 18:16:18Z 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 <string.h> #include "common.h" #include "configfile.h" #include "content.h" #include "control.h" #include "database.h" #include "group.h" #include "itemlist.h" #include "log.h" #include "outgoing.h" #include "over.h" #include "portable.h" #include "post.h" #include "protocol.h" #include "util.h" #define BEGIN_SIG "-- " #define SIG_FILE "/.signature" struct OverInfo { Str subject; Str from; Str date; Str msgId; Str ref; size_t bytes; size_t lines; }; struct Article { DynStr *text; /* Processed article text */ ItemList *newsgroups; /* Newsgroups for dispatch */ 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, 0, { "", "", "", "", "", 0, 0 } }; /* Add the article to a group. */ static Bool addToGroup( const char * grp ) { Over * over; const char *msgId; over = new_Over( article.over.subject, article.over.from, article.over.date, article.over.msgId, article.over.ref, article.over.bytes, article.over.lines ); msgId = article.over.msgId; Cont_read( grp ); Cont_app( over ); /* Cont modules owns ov after this */ Log_dbg( LOG_DBG_POST, "Added message '%s' to group '%s'.", msgId, grp ); if ( !article.posted ) { Log_inf( "Added '%s' to database.", msgId ); if ( ! Db_prepareEntry( over, Cont_grp(), Cont_last() ) || ! Db_storeArt ( msgId, DynStr_str( article.text ) ) ) return FALSE; article.posted = TRUE; } else { Str t; const char *xref; xref = Db_xref( msgId ); Log_dbg( LOG_DBG_POST, "Adding '%s' to Xref of '%s'", grp, msgId ); snprintf( t, MAXCHAR, "%s %s:%i", xref, grp, Ov_numb( over ) ); Db_setXref( msgId, t ); } if ( Cont_write() ) { Grp_setFirstLast( Cont_grp(), Cont_first(), Cont_last() ); Grp_setLastPostTime( Cont_grp() ); return TRUE; } else return FALSE; } static Bool checkPostableNewsgroup( void ) { const char * grp; Bool knownGrp = FALSE; Bool postAllowedGrp = TRUE; Bool local; /* * Check all known groups are writeable, and there is * at least one known group. */ for( grp = Itl_first( article.newsgroups ); postAllowedGrp && grp != NULL; grp = Itl_next( article.newsgroups ) ) { if ( Grp_exists( grp ) ) { local = Grp_local( grp ); knownGrp = TRUE; switch( Grp_postAllow( grp ) ) { case 'n': postAllowedGrp = FALSE; break; case 'y': break; case 'm': /* * Can post to moderated groups if *either* * 1. Group is local and article approved, or * 2. Group is external */ postAllowedGrp = ! local || article.approved; break; default: /* * Unknown mode for local groups. Forward * to server for external groups; presumably the * server knows what to do. */ postAllowedGrp = ! local; break; } } } if ( ! knownGrp ) { Log_err( "No known group in Newsgroups header field" ); return FALSE; } else if ( ! postAllowedGrp ) { Log_err( "A group does not permit posting" ); return FALSE; } return TRUE; } /* Get article text, check for validity & build overview. */ static Bool getArticleText( const char *p ) { DynStr * s; Str line, field, value; Bool dateFound, fromFound, msgIdFound, subjectFound; Bool newsgroupsFound, pathFound; Bool replyToFound, orgFound; Bool continuation; time_t t; int sigLines; s = new_DynStr( 10000 ); article.text = s; /* RFC says only one of these headers. */ dateFound = fromFound = msgIdFound = subjectFound = newsgroupsFound = pathFound = FALSE; /* Stuff we might want to add. */ replyToFound = orgFound = FALSE; field[ 0 ] = '\0'; /* * Grab header lines first, getting overview info as we go. * Note that a line may be a continuation line, hence we always * cat the information into the destination. */ while ( ( p = Utl_getHeaderLn( line, p ) ) != NULL && line[ 0 ] != '\0' && Prt_getField( field, value, &continuation, line ) ) { if ( field [ 0 ] == '\0' ) { /* Error! Continuation without preceding header. */ Log_err( "First header line started with white space" ); return FALSE; } /* Look for headers we need to stash. */ if ( strcmp( field, "subject" ) == 0 ) { if ( !continuation && subjectFound ) { Log_err( "Duplicate Subject: header" ); return FALSE; } Utl_catStr( article.over.subject, value ); DynStr_appLn( s, line ); subjectFound = TRUE; } else if ( strcmp ( field, "from" ) == 0 ) { if ( !continuation && fromFound ) { Log_err( "Duplicate From: header" ); return FALSE; } Utl_catStr( article.over.from, value ); DynStr_appLn( s, line ); fromFound = TRUE; } else if ( strcmp ( field, "date" ) == 0 ) { if ( !continuation && dateFound ) { Log_err( "Duplicate Date: header" ); return FALSE; } Utl_catStr( article.over.date, value ); dateFound = TRUE; } else if ( strcmp ( field, "references" ) == 0 ) { Utl_catStr( article.over.ref, value ); DynStr_appLn( s, line ); } else if ( strcmp ( field, "message-id" ) == 0 ) { if ( !continuation && msgIdFound ) { Log_err( "Duplicate Message-Id: header" ); return FALSE; } Utl_catStr( article.over.msgId, value ); msgIdFound = TRUE; } else if ( strcmp ( field, "newsgroups" ) == 0 ) { if ( !continuation && newsgroupsFound ) { Log_err( "Duplicate Newsgroups: header" ); return FALSE; } article.newsgroups = new_Itl( value, " ,\n\t" ); DynStr_appLn( s, line ); newsgroupsFound = TRUE; } else if ( strcmp ( field, "control" ) == 0 ) { article.control = new_Itl( value, " " ); DynStr_appLn( s, line ); } else if ( strcmp ( field, "reply-to" ) == 0 ) { replyToFound = TRUE; DynStr_appLn( s, line ); } else if ( strcmp ( field, "approved" ) == 0 ) { article.approved = TRUE; DynStr_appLn( s, line ); } else if ( strcmp ( field, "path" ) == 0 ) { if ( !continuation && pathFound ) { Log_err( "Duplicate Path: header" ); return FALSE; } 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: " ); DynStr_appLn( s, value ); } else if ( strcmp ( field, "xref" ) == 0 ) Log_inf( "Xref header in post ignored" ); else DynStr_appLn( s, line ); } /* Now sort header-related issues */ if ( article.over.from[ 0 ] == '\0' ) { if ( article.flags & POST_ADD_FROM ) { Log_dbg( LOG_DBG_POST, "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 ( ! subjectFound ) { Log_err( "Posted message has no Subject field" ); return FALSE; } if ( article.newsgroups == NULL || Itl_count( article.newsgroups) == 0 ) { Log_err( "Posted message has no valid Newsgroups field" ); return FALSE; } /* Ensure correctly formatted date */ t = Utl_parseNewsDate( article.over.date ); if ( t == (time_t) -1 ) { time( &t ); Utl_newsDate( t, article.over.date ); } DynStr_app( s, "Date: " ); DynStr_appLn( s, article.over.date ); /* Ensure Message ID is present and valid */ if ( article.over.msgId[ 0 ] == '\0' ) { Prt_genMsgId( article.over.msgId, article.over.from, "NOFFLE" ); Log_inf( "Adding missing Message-ID '%s'", article.over.msgId ); } else if ( ! Prt_isValidMsgId( article.over.msgId ) || Cfg_replaceMsgId() ) { Prt_genMsgId( article.over.msgId, article.over.from, "NOFFLE" ); Log_dbg( LOG_DBG_POST, "Replacing Message-ID with '%s'", article.over.msgId ); } DynStr_app( s, "Message-ID: " ); DynStr_appLn( s, article.over.msgId ); /* Ensure Path header */ if ( ! pathFound ) { Str path; Log_dbg( LOG_DBG_POST, "Adding Path field to posted message." ); DynStr_app( s, "Path: " ); Utl_cpyStr( path, Cfg_pathHeader() ); if ( path[ 0 ] == '\0' ) Prt_genPathHdr( path, article.over.from ); DynStr_appLn( s, path ); } /* Ensure Reply-To header */ if ( ! replyToFound ) { Log_dbg( LOG_DBG_POST, "Adding Reply-To field to posted message." ); DynStr_app( s, "Reply-To: " ); 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( LOG_DBG_POST, "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' ) { Log_err( "Posted message has no body" ); return FALSE; } /* Add the empty line separating header and body */ DynStr_appLn( s, "" ); /* Now pop on the rest of the body */ DynStr_app( s, 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( LOG_DBG_POST, "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 ); return TRUE; } /* Add article to outgoing if needs be */ static Bool postExternal( void ) { const char * grp; Str serversSeen; Bool err; /* * For each external group, send to that group's server if it has * not seen the post already. */ serversSeen[ 0 ] = '\0'; err = FALSE; for ( grp = Itl_first( article.newsgroups ); grp != NULL; grp = Itl_next( article.newsgroups ) ) { if ( Grp_exists( grp ) && ! Grp_local( grp ) ) { const char * servName = Grp_server( grp ); if ( strstr( serversSeen, servName ) != NULL ) continue; if ( ! Out_add( servName, article.over.msgId, article.text ) ) { Log_err( "Cannot add posted article to outgoing directory" ); err = TRUE; } Utl_catStr( serversSeen, " " ); Utl_catStr( serversSeen, servName ); } } return err; } /* Cancel and return TRUE if need to send cancel message on to server. */ static Bool controlCancel( const char *cancelId ) { return ( Ctrl_cancel( cancelId ) == CANCEL_NEEDS_MSG ); } /* It's a control message. Currently we only know about 'cancel' messages; others are passed on for outside groups, and logged as ignored for local groups. */ static Bool handleControl( void ) { const char *grp; const char *op; op = Itl_first( article.control ); if ( op == NULL ) { Log_err( "Malformed control line." ); return TRUE; } else if ( strcasecmp( op, "cancel" ) == 0 ) { if ( ! controlCancel( Itl_next( article.control ) ) ) return TRUE; /* Handled entirely locally */ } else { /* Log 'can't do' for internal groups. */ for( grp = Itl_first( article.newsgroups ); grp != NULL; grp = Itl_next( article.newsgroups ) ) { if ( Grp_exists( grp ) && Grp_local( grp ) ) Log_inf( "Ignoring control '%s' for '%s'.", op, grp ); } } return postExternal(); } static Bool postArticle( void ) { const char *grp; Bool err; Bool local; Bool postLocal; err = FALSE; postLocal = Cfg_postLocal(); /* * Run round first doing all local groups. * Remember, we've already checked it is OK to post to them all. */ for( grp = Itl_first( article.newsgroups ); grp != NULL; grp = Itl_next( article.newsgroups ) ) { local = Grp_local( grp ); /* * Only post locally to external group if that group's post * status is 'y'. Otherwise retrieve from upstream server - * for example, we don't want to immediately post locally articles * destined for the moderator of a moderated group. */ if ( local || ( postLocal && Grp_postAllow( grp ) == 'y' ) ) err = addToGroup( grp ) && err; } return postExternal() && err; } static void clearArticleInfo( void ) { article.text = NULL; article.newsgroups = NULL; article.control = NULL; article.approved = FALSE; article.posted = FALSE; article.flags = 0; memset( &article.over, 0, sizeof( article.over ) ); } /* Register an article for posting. */ Bool Post_open( const char * text, unsigned flags ) { if ( article.text != NULL ) { Log_err( "Busy article in Post_open." ); return FALSE; } clearArticleInfo(); article.flags = flags; if ( ! getArticleText( text ) ) return FALSE; if ( Db_contains( article.over.msgId ) ) { Post_close(); Log_err( "Duplicate article %s.", article.over.msgId ); return FALSE; } return TRUE; } /* Process the posting */ Bool Post_post( void ) { if ( article.flags & POST_DEBUG ) { fputs( DynStr_str( article.text ), stdout ); return TRUE; } if ( ! checkPostableNewsgroup() ) return FALSE; return ( article.control == NULL ) ? ! postArticle() : ! handleControl(); } /* Done with article - tidy up. */ void Post_close( void ) { if ( article.text != NULL ) { del_DynStr( article.text ); article.text = NULL; } if ( article.newsgroups != NULL ) { del_Itl( article.newsgroups ); article.newsgroups = NULL; } if ( article.control != NULL ) { del_Itl( article.control ); article.control = NULL; } }