pax_global_header00006660000000000000000000000064133303356200014510gustar00rootroot0000000000000052 comment=cd5edbb1160d86033d20735eec0500dceca30fdd suck-4.3.4/000077500000000000000000000000001333033562000124655ustar00rootroot00000000000000suck-4.3.4/.gitignore000066400000000000000000000001561333033562000144570ustar00rootroot00000000000000*.o *.rej *.orig configure config.* Makefile autom4te.cache/ phrases.h makephrases lmove rpost suck testhost suck-4.3.4/CHANGELOG000066400000000000000000002046051333033562000137060ustar00rootroot00000000000000CHANGES ======= -- Changes from suck-4.3.3 -> Suck-4.3.4 - Fix invalid memory access in both.c (Debian bug #858040) - Fix SIGSEV in connect_to_nntphost (Debian bugs #716448, #716510, #716606) - Fix stack smash in lmove.c and suckutils.c (Debian bug #832265) - Fix manpage formatting (Debian bug #858262) August 01, 2018 - Suck-4.3.4 released -- Changes from suck-4.3.2 -> Suck-4.3.3 - General code cleanup - Applying patches from Debian, Fedora and openSUSE - Add support for IPv6 - Manpage fixes - Default path for history file is now /var/lib/news/history - Default path for phrases file is now /etc/suck/phrases - Install to DESTDIR - Don't strip binaries October 25, 2017 - Suck-4.3.3 released -- Changes from suck-4.3.1 -> Suck-4.3.2 Wherever a hostname is specified on the command line, the form Host:Port may be used to specify a port number. If this form is used, the -N port option will be ignored. configure.in - add -with-perl-exe option to specify an alternate path to the perl exe. suck.c, killfile.c - get_a_chunk(), get_chunk_mem() changed to handle lines beginning with . correctly in stdout mode. suck.c, rpost.c - main() - error handling if host isn't specified on command line. * - various patches from Debian folks configure.in - check for configdata.h in inn include directory both.c - sgetline() - changes to fix SSL bug with the select, connect_to_nntphost() - add code to handle host:port syntax. This fix also allows you to specify a port number for the local host. suck.c - to handle SIGINT in addition to SIGTERM, build_command() fix in potential buffer overflow testhost.c - added -Q option, to allow user to specify NNTP authentiation via env variables. March 28, 2003 - Suck-4.3.2 released -- Changes from Suck-4.3.0 -> Suck-4.3.1 * as of this version, my e-mail address has changed to bobyetman@sucknews.org * - added stuff to autoconf to handle the Db, history, Perl & SSL stuff in the Makefile. * both.h - added tests for socks.h * rpost.c - added -Q option, to allow user to specify NNTP authentication via environment variables - do_batch() - add test to not append prefix if article name is an Inn token. * suck.c - added -Q option, to allow user to specify NNTP authentication via environment variables - build_command() - changed sprintfs to snprintf() to fix potential overflow problems. -- get_one_article() - fix bug where plist would be null when doing authentication and there was only one article to download. - main() - fix bug where if reconnect after dedupe, and it failed, you would try to send the quit command to an invalid file descriptor. Sep 24, 2002, Suck-4.3.1 released -- Changes from Suck-4.2.5 -> Suck-4.3.0 * As of this version, my e-mail address has changed from from bobyetman@worldnet.att.net to bobyetman@home.com * changes to allow it to use SSL. * rpost.c - added -i option, to ignore the readonly opening response and try to post anyway, since inn-2.3, when using authinfo, still sends 201 code meaning readonly. - do_authenticate() - added print statements to show start of, and successful completion of authentication. * suck.c - get_one_article_kill() - add test for header_only mode when in stdout mode to not download the body. - get_one_article(), get_articles() - added -bP option, to post articles during download. Sep 17, 2001 - Suck-4.3.0 released -- Changes from Suck-4.2.4 -> Suck-4.2.5 * batch.c - post_one_msg() - fixed bug in testing for a long input line that caused us to reject any line that is exactly 13 char long. * chhistory_db.c - various changes to allow it to compile with Inn-2.3 headers. * rpost.c - do_article() - added test for second duplicate article string to handle DNews. - filter_batch_article() - added option for showing the file name as we upload it. Dec 02, 2000 - Suck-4.2.5 released -- Changes from Suck-4.2.3 -> Suck-4.2.4 * various changes to allow compile under OS/2 (look for ifdef EMX). You'll need to edit the Makefile to get it to compile. *both.c - true_str(), null_str() - moved from suck.c so rpost can use. * killprg.c - Changes to include so will compile using Perl5.6. If you're using perl5.004 or older, you'll need to define OLD_PERL in the Makefile. * rpost.c - Changes so will compile with perl5.6.0. do_article() - Added actual error message from remote server to Malfunction error message, to make it easier to figure out problem. * suck.c - do_one_group() - added code for resetcounter option. If remote end resets its article numbers the normal suck response is to ignore the group and reset the lastread counter to match the current high counter. This option tells suck to reset its lastread counter to the low counter, effectively causing suck to get all articles for the group, and use the historydb to check for dupes. Added low_read option. This option is used in concert with the maxread option of the sucknewsrc. Normally when there is a maxread in the sucknewsrc, suck will download the newest articles. This option tells suck to download instead the oldest articles. do_articles() - added code for show_group option. This option will add the name of the current group to the BPS display as you download the articles. May 21, 2000 - Suck-4.2.4 released -- Changes from Suck-4.2.2 -> Suck-4.2.3 * both.c - signal_block() - Changes to get rid of invalid #ifdef POSIX_SIGNAL causing this routine never to get called. * killfile.c. parse_killfile() - fixed bug in getting perl filter filename. If you didn't specify debug, the nl was getting left on the name, causing errors. * rpost.c - * - Major internal re-write to handle future expansion of filters/batch mode. Changed many function's arg passing, so they only pass around myargs. Re-wrote batch mode to make adding new filters easier. Added rnews mode, for reading rnews files. See man page for more details. * suck.c - main(), sighandler() - changes from signal() to sigaction() so I use all POSIX signal handling stuff. - do_nodownload() - added, this routine allows you to specifiy Message-IDs to never download. See the SUCKNODOWNLOAD section in the man page. 23 Jan 2000 - Suck-4.2.3 released -- Changes from Suck-4.2.1 -> Suck-4.2.2 * lmove.c match_group(), move_msgs(), scan_args() - Re-wrote for new options -h and -s to create hard or symbolic links to files crossposted to multiple groups. Re-wrote code to handle malformed header with spaces either before or after the newsgroup names. load_active() - added test for duplicate group line. * suck.c - do_one_group() - changed command from "xhdr 111-" to "xhdr 111-2222" for brain-dead servers that don't follow the proposed standard and treat 111- correctly. * xover.c - do_xover(), do_group_xover() - changed command from "xover 111-" to "xover 111-222". 25 July 1999 - Suck-4.2.2 released -- Changes from Suck-4.2.0 -> Suck-4.2.1 * rpost.c - do_perl() - fixed bug where infile was being lost due to FREETMPS, causing failed posting. * suck.c - restart_yn() - fixed bug where if prior to restart, we had sent command for an article, but not received it, then on restart, we would skip article. So I have to set sentcmd to false if we haven't downloaded it. * xover.c - chk_a_group() - rewrote into one loop, to get rid of segfaults on NULL field, and to speed things up for complicated xover files. 23 May 1999 - Suck-4.2.1 released -- Changes from Suck-4.1.2 -> Suck-4.2.0 * active.c - get_msgids() - added new option, -i, to set the default number of articles to download when using the -A or -AL option and a new group is added. See man page for usage. do_one_group() - changed handling of return from do_group_xover() so handle new error code. parse_args(), main() - added code to handle -i option. get_articles() - fixed bug in handling of pause and reconnect after X nr of articles. Wasn't taken into account 0 articles correctly. * killfile.c - moved xoverview pointer out of master killfile and into master struct, so can use independently of killfiles (for -Z option). parse_killfile() - changed to passed which argument to various setup routines, so can distinguish between XOVER and regular killfiles. * killprg.c - killprg_forkit(), killperl_setup() - added test for which, so if XOVER killfiles we don't point the killfile functions to killprg stuff. - killprg_sendoverview(), killprg_sendxover, killperl_sendxover() - created. These routines handle the sending of the overview.fmt and each overview line to a child program or perl subroutine for matching. * suck.c - main() - added code to handle retreival and freeing up of memory used by overview.fmt. Added call to free up xoverp killfile.u - do_one_group() - added code to handle new option, -Z, to use XOVER vice XHDR to get message-ids, in case the remote server doesn't support the XHDR command. * xover.c - do_group_xover() - changed the error code returned if server can't do xover command, so can recover gracefully from other errors (such as too long msgid) get_xover(), find_msgid() - created. These routines use the XOVER command to get the Message-ID and alloc it for the -Z option. Moved xoverview pointer out of master killfile and into master struct, so can use for -Z option. - do_one_line() - added code so if we have a program or perl subroutine to do the checking, it gets called 11 May 1999 - suck-4.2.0 released -- Changes from Suck-4.1.1 -> Suck-4.1.2 * suck.c -- do_supplemental() - added call to do_sup_bynr(). do_sup_bynr() - created. Handles lines in suckothermsgs that specify a group name and article number for retreival. 14 Apr 1999 - suck-4.1.2 released -- Changes from Suck-4.1.0 -> Suck-4.1.1 * suck.c - get_one_article() - fixed bug in sending command for the second article, was resending first article command. 27 Mar 1999 - suck-4.1.1 released -- Changes from Suck-4.0.0 -> Suck-4.1.0 * killfile.c - get_one_article_kill() - added code to handle new option, -g, to get only the headers of articles. See man pages for details. * suck.c - get_one_article() - added code to handle new option -g, to get only the headers of articles. * xover.c - chk_a_group() - fix bug in counting of Xref line, causing inaccurate count. do_one_line(), get_xoverview() - fixed handling of :full flag in overview.fmt. 22 Mar 99 - suck-4.1.0 released -- Changes from Suck-3.10.4 -> Suck-4.0.0 * reworked restart code. Got rid of suck.restart and suck.sorted, replaced with suck.db, which contains the records of all messages. All of this code is now contained in db.c. Cleaned up the use of the Mandatory field by creating separate delete and sentcmd fields. * chkhistory.c chkhistory() - fixed bug which caused no articles to be checked, I wasn't passing the current article to my_bsearch(). chkhistory() - changed HISTORY_FILE to a run time option vice the constant defined in the Makefile. * chkhistory_db.c chkhistory(), open_history() - changed HISTORY_FILE to a run_time option. * killfile.c - added code to handle new killfile option NRXREF. This works the same as NRGRPS, but on the Xref line instead of the Newsgroups line. killfile_done() - fixed bug in freeing perl_int. check_a_group() - fixed bug in counting nr of groups. pass_two() - fix bug if groupline didn't have group name, we could core dump when trying to nuke nl. * ssort.c - changed params that my_bsearch is called with, to make chkhistory faster. Changed second param to string vice struct. * suck.c - do_cleanup() - fixed a bug with moving suck.newrc to sucknewsrc. If we're restarted with -R, no suck.newrc would exist, and we'd move sucknewsrc to sucknewsrc.old, and fail on the move of suck.newrc to sucknewsrc. Now if suck.newrc doesn't exist, we don't move sucknewsrc either. parse_args() - added code to handle history file option (HF). * xover.c - added code to handle to option NRXREF, like above. Also added code to handle new option XOVER_LOG_LONG, which causes suck to format xover kills so that they look like message headers, instead of printing just the Xover line. 13 Mar 99 - Suck-4.0.0 released -- Changes from Suck-3.10.3 -> Suck-3.10.4 * active.c - get_message_index_active() - added code to handle -F option, reconnect after reading the active file. This is in case of a large active file, and the remote end times out while reading it. * killprg.c - tweaked the #ifdef for the Perl 5.004 stuff * lmove.c - move_msg(), scan_args() - Added test to see if article exists in new location before I move it. This is to avoid overwriting articles if another process adds articles to directory without changing the active file. If an article already exists, I abort UNLESS you use the -A option. See man page for more details. main() - rewrote tests for msgdir and basedir, to avoid segfaults if not defined. * suck.c - main(), scan_args() - added code to handle -F option. * xover.c - get_xoverview() - fixed bug in parsing of xoverview, not stripping the blanks and nls correctly. Also fixed bug in allocing of memory (had POverview vice Overview). 25 Jan 1999 - Suck-3.10.4 released -- Changes from Suck-3.10.2 -> Suck-3.10.3 * batch.c - do_post_filter() - created, allows you to edit all articles downloaded. *batch.c - do_lmove_patch() *killprg.c - killprg_forkit() *rpost.c - do_filter - added exit(-1) so that if child doesn't execl, we don't have both parent and child running. * chkhistory.c - chkhistory() - changed error_log() call to MyPerror() for open of history file, so get more descriptive error msg. * suck.c - main(), parse_args() - added arg handling for do_post_filter(). get_articles(), restart_yn() - re-worked the handling of restarts, to avoid downloading same article twice, or not downloading an article, due to the de-duping process when I did a rescan for new articles, so I had the same MsgId twice, then deleted the first one already downloaded, and then downloaded it again. * dedupe.c - dedupe_list() - added code so that if one of a set of dupes is marked as downloaded, or a dupe, I delete the other one instead, to help avoid the scenario above. 10 Dec 98 - Suck-3.10.3 released -- Changes from Suck-3.10.1 -> Suck-3.10.2 * chkhistory.c - chkhistory() - changed to new, hopefully faster routine, using ssort() and my_bsearch(). * dedupe.c - dedupe_list() - changed to use new routine to handle deduping based on the ssort() routine. * ssort.c - created, contains ssort() and my_search(). * suckutils.c - qcmp_msgid() - created for use by my_bsearch(). * xover.c - do_group_xover(), do_one_line() - rewrote to fix bug in not handling multiple group keep/delete files. 8 Nov 98 - Suck-3.10.2 released -- Changes from Suck-3.10.0 -> Suck-3.10.1 * chkhistory_db.c - open_history() - fixed typo in GDBM code had == where it should have been = . * killprg.c - added #define ERRSV and PL_na, since Perl 5.004 and earlier don't have these. * rpost.c - main(), scan_args() - modified to handle perl embedded filter. * rpost.c - parse_perl(), perl_done(), do_perl() - created, handles embedded perl subroutine for filtering files uploaded. * xover.c - get_xoverview() - fix bug in not initializing elements of the linked list (next = NULL). 13 Oct 98 - Suck-3.10.1 released -- Changes from Suck-3.9.4 -> Suck-3.10.0 * both.c - added tests for and for BEos, it needs net/socket.h, everyone else needs the other one. error_log(), do_debug_vl() - added code to handle printing of error messages to do_debug(). * chkhistory.c - chkhistory() - added code to check for valid lines, since INN now has lines that begin with [, used by the DBZ code, and we need to ignore them, and use only the lines that start with <. * chkhistory_db.c - open_history(), close_history() - added code for using GDBM database. * killfile.c - parse_killfile(), do_debug() - changed to pointer and malloc for master killfile, so can re-use routine to load in XOVER killfiles. Added filename parameter, so can tell it what file to parse. Changed TRUE_STR() macro to use true_str function from suck.c parse_a_file() - added initialization of bodybig and bodysmall. parse_killfile(), parse_one_file() - changed so that if a killfile evaluates to empty, we throw it away, so we don't spend time finding groups for empty killfiles. parse_killfile(), free_killfile() - changed to set up for embeded perl killfile call. * killprg.c - killperl_setup(), killperl_done(), chk_msg_kill_perl() - created, handles calling embedded perl subroutine for killfiles. * lpost.c - added include to fix compile bug on SunOS. * rpost.c - do_article() - added code to test for a string to confirm a duplicate post error message, since some servers don't use a separate error code to dilineate these from other failures. scan_args() - added call to error_log() so that error_log and MyPerror messages also go to debug.suck, if debug is true. * suck.c - main(), get_articles(), parse_args() - added code to change the format of the BPS and count output to make it easier for a gui to pick these out and display separately. For use in my java gui, or anything else for that matter. Added arg -G to toggle this on. main() - changed calls to true_str() and null_str() from macros to true functions, since they are used so much in debug code, so as not not have 300 copies of "true" "false" and "null" in the program. Added -f option, to reconnect after deduping the messages, in case of long dedupes, and the remote disconnects due to time outs. main(), parse_args() allocnode(), do_one_group() - added code to handle the new XOVER arg, to parse the xover killfiles, to allow passing of article number, and to do the xover() call. parse_args() - added call so if debug is set, error_log and MyPerror reports to debug.suck. * testhost.c - main() - added -o option, to list the overview format, which is what the XOVER command returns. Added -M option, in case you need the mode reader command for the -o option to work. * xover.c - created, holds code to process XOVER killfiles. 5 Oct 98 - Suck-3.10.0 released -- Changes from Suck-3.9.3 -> Suck-3.9.4 * active.c - add_to_list() - added code to do regular expression testing, if your system supports regex(). - read_ignore() - added code to compile regular expressions if your system supports regex(). *chkhistory_db.c - added code to handle INN-2.0's new database routines. * makephrases.c - count_vars() - rewrote so can't go past end of string when checking for a variable. Added . * suck.c - do_one_group() - changed so abort on non 411,500 error codes when sending the group command. 23 Jul 98 - Suck-3.9.4 released -- Changes from Suck-3.9.2 -> Suck-3.9.3 * suck.c - main() - added code to get reply from "quit" command, that way if remote end is in process of sending us something, we don't close down the buffer prematurely. 23 Apr 98 - Suck-3.9.3 released -- Changes from Suck-3.9.1 -> Suck-3.9.2 * As of this version, my e-mail address has changed from boby@pixi.com to bobyetman@worldnet.att.net. killprg.c - chk_msg_kill_fork() - added code to handle new -LF option, to override built-in default for suck.killlog. killfile.c - get_chunk_mem() - changed so that if send_command() aborts due to TimeOut, we return the actual error (RETVAL_ERROR) vice UNEXPECTEDANS which would cause us to keep going. regex_scan() - fixed bug if you had were using QUOTECHAR and had regex() compiled in. The regex code would strip the QUOTECHAR off the string, so we'd always do case insensitive compare. parse_a_file(), regex_scan(), regex_check(), regex_block(), do_debug() - modified to handle new non-regex searching mechanism. Switched to Boyer-Moore algorithm vice straight brute force of strstr() or nstrstr(). Should make searching of body of messages lots quicker. Added ability to designate string in a killfile as a non-regex. This is done by adding a % to the front of a string, before the QUOTE char. Added NON_REGEX= param to killfiles. Allows you to specify a character instead of % to represent non-regex strings. Revamped the handling of the QUOTE character, it's all done now in regex_scan(). Fixed bug in printing if we were using extended regex or not. Added debugging statements. chk_msg_kill() - added code to handle new -LF option, to override built-in default for suck.killlog. suck.c - do_one_group() - added error message print if timed out getting MsgIds. do_connect(), send_command() - fixed to handle flushing of inbuf and resetting which group number we are on, if reconnect option is given. restart_yn() - added -O, skip_on_restart. Useful when suck is having trouble with a particular article, and it keeps timing out when you restart. This option tells suck to skip the first article on restart. 29 Mar 98 - Suck-3.9.2 released -- Changes from Suck-3.9.0 -> Suck-3.9.1 both.c - get_long() - created, copy of number() except it gets longs instead of ints. str_long() - created copy of str_int() except it gets longs instead of ints. suck.h - changed so MsgNr is long vice int. This necessitated a bunch of changes in other routines, changing int vars to long, and a couple of subroutine calls had to be changed. 12 Feb 98 - Suck-3.9.1 released -- Changes from Suck-3.8.0 -> Suck-3.9.0 Makefile.in - remove -ansi -pedantic, which caused Linux Glibc and other systems problems. active.c - nntp_active(), read_active() - created, read activelist from NNTP host or local file, respectively. get_message_index_active() - changed to call nntp_active() or read_active(). get_msgids() - add nl to print of original line, so sucknewsrc looks okay. batch.c - do_rnewsbatch() - added code to test for existence of batch file and fail if it already exists. Added code to test for write error. Redo fclose()s so they are in right place. do_innbatch() - added code to test for write error. both.c - do_debug_binary() - created, do a dump without filtering thru a print. Needed in case got NULL in string. findnl() - created, replaces strstr() call in sgetline() so can handle NULLS in messages. sgetline() - added call to do_debug_binary() - so can see if I get NULL in recv(). killfile.c - get_one_article_kill() - added code to test if failed to write header to disk. Modified to handle new calling sequence for chk_msg_kill*(). Added code to get the body of the article, if it wasn't already downloaded in check_a_group(). Added code to handle UNEXPECTEDANS from get_chunk_mem. chk_msg_kill() - modified to handle new calling sequence, so can pass master to check_a_group(), get_chunk_mem() - renamed from get_a_chunk_mem(). Added size param to track size of buffer. Changed strcpy() to memmove() in case of nulls in string. Added code to get both the header and body of articles into separate buffers. Added NULL to end of buf so that logging is correct. Added code to handle retval and to pass back UNEXPECTANS if the article is not available on server. check_a_group() - added code to test body and bodysize. Moved some code down to regex_block(). recoded to handle multiple body and header checks. parse_a_file, do_debug() - added code to handle body and bodysize> and bodysize< parameters. added code to handle multiple header and body checks. added code to handle extended regex parameter. regex_block() - created. This code will check an entire block (header/body) for the string/regex desired. debug_one_kill() - created, print OneKill structrure, moved some code from print_debug() here to make printouts more uniform and code read a bit easier. regex_scan() - fixed bug in error message for bad regular expression. Added code to free up structure if error in routine. Added code to handle use extended regex parameter. pass_one() - added code to handle use extended regex parameter. killprg.c - chk_msg_kill_fork() - modified to handle new calling sequence, needed cause chg_msg_kill needed master. lmove.c - read_active(), rewrite_active() - swap lownr and highnr params around to match real format of active. load_phrases() - added code to test for correct version nr, fixed bug in not skipping ahead to lmove phrases. find_groups() - fix bug by changing linein[] to static. makephrases.c - added code to write out version number to phrase file. rpost.c - added tests to various sgetlines() in case it aborts due to timeout. load_phrases() - added code to test for version number. do_batch() - added code to write rest of articles to failed file if we abort due to loss of connection. suck_config.h - Move DEBUG1 from suck_config.h to Makefile debug_both suck.c - main() - added TRUE_STR() and NULL_STR() macros to clean up the debugging printout a bit. Fix bug in errormsg if localhost isn't defined. Moved printout of debug stuff so it prints even if there's an error in the parsing of the args. Added code to handle chk_msgid option. Added code to handle -AL option, read activelist from file. Added code to handle -B option, check for any lingering messages and batch em up before we start the main run. do_supplemental() - added -z option don't do dedupe. This is primarily for real slow machines where the dedupe process is longer than the time to download the messages. I don't recommend this option. allocnode() - added code to handle -x option, chk_msgid. If this option is set suck doesn't check the end of the MsgId to verify that there is the > that is supposed to be there. This is for some brain-dead NNTP servers that truncate XHDR info at 72 characters. Fixed bug introduced when added code to get message number that cause the supplemental file to not be processed. get_a_chunk() - change fputs to fwrite(), to handle NULLs. Added code to test retval of fwrite() in case disk is full. load_phrases() - added code to test for correct version nr, fixed bug in not skipping lmove phrases. get_articles() - move test for mandatory down to killfile code, due to pipelining in non-killfile code. testhost.c - main() - added -q option, suppress connect and announcement displays, only show result of command load_phrases() - added code to test for correct version number. 5 Feb 98 - suck-3.9.0 released -- Changes from Suck-3.7.0 -> Suck-3.8.0 active.c - read_ignore() - move fclose() to prevent attempt to close file that's NULL. batch.c - batch_lmove() - changed batchfile option to pass the configuration file name vice the activefile name. lmove now uses the configuration file to get the activefile name. killfile.c - parse_a_file() - added code to handle comment character. lmove.c - main(), rewrite_active(), load_active(), scan_args() - added -c option which allows you to specify configuration file name. Rewrote so that active file looks like standard news active file, which group name, low, high, and status fields. This means I had to move BASE= to another file, the config file. This also loads ACTIVE= to specify the location of the active file. load_config() - created, loads configuration file. rpost.c - main(), send_command() - added code to handle the new -u option, auto_authenticate. This option automatically sends the authinfo user command upon connect to the remote host. do_authenticate() - added, used by -u option. send_command() and do_authenticate() are carbon copies of what is in suck.c, except for variable name changes. suck.c - get_one_article() - move output of final '.' so that it only happens if the article is successfully downloaded. main(), allocnode(), do_free(), do_one_group(), do_supplemental(), get_one_article(), get_one_article_kill(), restart_yn(), parse_args() - (wow) Added code to handle -n option. This option retrieves articles by the Number, not by the MessageID. This means I had to track which group each article was in, and send the command to switch groups when needed. This gets tricky because of the pipelining code, so I can't send ahead if I have to switch groups. If your remote NNTP server doesn't send the article number with the xhdr command, then this option is disabled in the code. get_group_number() - created, adds group to list and assigns nr for later retrieval in -n option. build_command() - created, builds head, article, or body command with either msgid or msgnr, depending on -n option. do_connect, scan_args() - added code to handle the -u, auto_authenticate option. This option will automatically send the authinfo user command upon connection. testhost.c - main() - added code to handle -d option, get group descriptions (send command 'list newsgroups'). 24 Nov 97 - suck-3.8.0 released -- Changes from Suck-3.6.0 -> Suck-3.7.0 Makefile.in - add configure section, change install so install_lpost does both program and man page, fix bug in in installall sample\* - various tweaks to use new -A and -bp options active.c - created, handles the -A option, reading in the local active file, and merging it with the existing sucknewsrc to get the msgids. both.c - sgetline() - added global variable TimeOut, to allow set of timeout value from all the programs. batch.c - created, moved all batch subroutines here. - do_localpost(), post_one_msg() - created, post articles to local server using IHAVE (-bp option) killfile.c - chk_msg_kill() - put NL between articles in killfile log when in long log mode. - get_one_article_kill() - added code for BATCH_LIHAVE - added to fix compile bug on glibc systems. rpost.c - scan_args() - added code to handle -T (timeout) option. suck.c - get_message_index() - rewritten, much of this code moved to do_one_group(), so that it can be called from active.c. Correctly handle a maxread of 0 and not download any msgs. - do_one_group() - created. - get_articles() - redid code that prints BPS to use new GET_BPS function from the timercode, so that I can correctly format the output and don't get BPSS type stuff. Added code to handle BATCH_LIHAVE file open. - get_one_article(), main(), added code for BATCH_LIHAVE - do_rnewsbatch(), do_innbatch, do_lmovebatch() - moved to batch.c - scan_args() - rewritten to handle long and short args - parse_args() - created, contains much of old scan_args(). Added code to handle -A, -bp, -hl, and -T (Timeout) options. - main() - added some arg checking code. - get_one_article(), do_authenticate() - fixed bug in not handling authentication due to send-ahead code. testhost.c - main() - added code to handle -T (timeout) option. timer.c - TimerFunc() - added GET_BPS option to return the BPS vice display it on the screen. 19 Oct 97 - suck-3.7.0 released -- Changes to Suck-3.5.2 *.c - removed all ifdef TIMER this option is no longer needed, now that the -q is present. both.c - added test for NULL fpo to print_phrases(). killfile.c - changed all routines, got rid of non-regex stuff, and rewrote to handle new killfile format and parameters. Changed get_one_article_kill() to put tmp message into TMPDIR not MSGDIR. Added -k option to ignore postfix for master killfile so can have one set of killfiles for multiple feeds. Changed so the killfile names in the GROUP lines are absolute, and don't get prefix added on. Changed so killfile log only opened once, and changed log format, added long and short version, and it also prints out which string we matched on. Added wildcard matching to the group matching routine (so can match comp.os.linux*). Handle space instead of comma for separator on newsgroup line. Added code to handle batch infeed option. Added test for regex or non-regex, so can use faster strstr() if not regex. Fixed bug so that if killfile don't exist, we just ignore it. killprg.c - chk_msg_fork_kill() - added logging to killfile log. Added various debugging statements, to help determine problems with child programs. - move #include before the , needs to be this order for FreeBSD. - chk_msg_kill_fork() - added new logging code in. suck.c - get_announcements() - added code to handle authentication. renamed to do_connect(), moved connect code down here, to allow for disconnect and reconnect every X msgs (to combat the inn LIKE_PULLERS=DONT option) send_command() - moved code for authentication to separate function. do_authenticate() - created to handle all authentication requests. scan_args() - re-organized so options in alphabetic order, so easier to figure out which letters are used. get_msg_index() - added # of msgs to the print out of the which articles are being downloaded. main() get_articles() - moved parsing of killfiles to main(), made killp part of master structure. Added code to handle -C option (reconnect every x msgs). Added code to handle batch innfeed option. get_one_article() _ changed to create tmp msg file in TMPDIR not MSGDIR, so rnews can feed directly off of MSGDIR. do_rnewsbatch(), do_innbatch() - changed so gathers all articles with the proper postfix. This is needed in case a restart gets more articles and the numbering changes. This means if you are NOT using a postfix, you'll need to make sure there is nothing else in the MSGDIR but the msgs, since we'll match on all files. Fixed bug in not printing ".\n" at end of message in stdout mode. Added code to handle batch innfeed option. main() - moved code to set up master.msgs to stdout or stderr to before lock_file() call, so that if in stdout mode, we print messages correctly. suckutils.c - move_file() - change typo in write from fpi to fpo. - tmp_path() - removed as was causing a bug in renaming the tmp articles back to the real names. timer.c - added ifdefs for HAVE_GETTIMEOFDAY in case the system doesn't have it. 7 Sep 97 - suck-3.6.0 released -- Changes to Suck-3.5.1 both.c - MyPerror() - added code to handle NULL ptr possibility killfile.c - regex_scan() - fixed bug in error_log report (forgot NULL) rpost.c - do_article() - added code to handle M$ 446 (615) response for duplicate article suck.c - main() - added code to handle -q option. do_rnewsbatch(), do_innbatch() - fixed possible overflow condition by changing size of tmp variable to MAX_PATH vice 20. get_articles() - added code to handle -q (quiet) option to not display BPS and article count. scan_args() - added code for -q option. get_message_index() - added test in case low and high article numbers from GROUP command don't jive (low > high) get_one_article() - changed nesting on the last if() so that if Multifile is FALSE we don't try to move files, since there aren't any to move. do_supplemental() - removed an unneeded call to TimerFunc(). Added test for no messages, so don't do deduping and history check. suck_config.h - added code for Solaris in case PATH_MAX wasn't defined to define it to MAXNAMLEN suck_utils.c - move_file() - added test in case either file is NULL ptr 1 Aug 97 - suck-3.5.2 released -- Changes to Suck-3.5.0 README - revamped a bit, to make col.announce posting a bit easier. killfile.c - get_one_article_kill() - change rename() to move_file(). lmove.c - added limits.h to fix problem on SCO machines with no PATH_MAX. rpost.c - main() -added code to set up default phrase file. suck.c - main() - added code to set up default phrase file "/usr/local/lib/suck.phrases" This also involved changing the *.doc/Makefiles to copy the phrase file to this location. Added call to setvbuf() to set buffering to line vice block. Added code to handle rescan arg, which says to skip rescan on a restart. - do_cleanup() - change rename() to move_file(). - do_innbatch(), - do_rnews(), - added postfix to file name checked for addition to batch file. - alloc_node(), - added code to initialize struct item sentcmd, to be used for pipelining to determine if we've sent command for this article or not. - get_one_article() - added code to do pipelining, that is send the command to get one article ahead, so that while we are reading one article, the next one is being fetched by the server. Changed rename() to move_file(). - scan_args() - added code to handle rescan argument -R - restart_yn() - fixed bug in error msg when suck.restart exists, but suck.sorted don't, changed it so this isn't a fatal error. suckutils.c - move_file() - created, move a file to another location, either via rename() or a manual copy. We do this so that if the move spans file systems, we can handle it gracefully. - full_path() - added FP_GET_POSTFIX option to get postfix testhost.c - main() - added code to set up default phrase file. 17 Jun 97 - suck-3.5.1 releaseed --Changes to Suck-3.4.1 *.c *.h - Changed DEBUG2 and DEBUG3 (debug suck and rpost) a run time option from compile time option. Now suck, lmove, and rpost support -D for debug. This necessitated changing all ifdefs DEBUG[23] to if(debug==TRUE).... and a few subroutines so debug info got passed. lmove.c lmove_phrases.c - created. This is the long-promised utility to move articles into the news/group/nr format. See man pages for more details. Makefile.in - changed VERSION from numbers to strings, rewrote so most lines < 80 characters for those folks using dumb terminals, added code so other language man pages can be installed directly from main Makefile. both.c - sgetline() - changed call to select() from FD_SETSIZE to fd+1 so that we only scan our own FD not all 1024. A few tweaks to debug statements to clean up a bit. chkhistory.c - removed CHECK_HISTORY_OLD, no longer needed. killfile.c - get_one_article_kill() - added call to tmp_path() so that current article being downloaded has a .tmp extension on it. suck.c - removed all code that handled -k (MSnews kludge option). main() - changed algorithm a bit so on restart, in addition to getting all old articles, check for any new articles and download them as well. Added code to handle call to do_lmovebatch. - get_one_article() - rewrite to not use send_command(). This is the start of adding the ability to pipeline commands and receipt of articles. Added call to tmp_path() so that current article being downloaded has a .tmp extension on it. - do_supplemental() - fix bug in call to TimerFunc(). - scan_args() - added code to handle call to do_lmovebatch(). - do_lmovebatch() - created. does a fork and execl to lmove, to put files in news/group/number format. suckutils.c - do_lockfile() - changed fprintf() to print_phrases() - add #include for BSDI systems and tweaked suck_config.h part where it handles PATH_MAX - tmp_path() - created, create a tmp file name with fullpath. 20 May 97 - suck-3.5.0 released --Changes to Suck-3.4.0 both.c - vprint_phrases() - rewrote to handle a couple of bugs, 1, if len = PHRASES_BLOCK_SIZE then no null termination, 2, if string arg > PHRASES_BLOCK_SIZE, infinite looped. suck.c - free_phrases() - fix typos of free_array() call for dedupe_phrases which caused a SIGBUS error. - get_message_index() - fix bugs in error_log() call for error messages for group command - main() - handle suck -V to correctly display version number without putting a hostname in. 30 Mar 97 - suck-3.4.1 released --Changes to Suck-3.3.2 *.* - Tweaks to various configs to make more portable. *.c - got rid of all lingering \r\n in log and status messages. Added #define before sys/stat.h in various .c files, for more portability (Ultrix needs this). phrases.h *phrases.c - created. This is the code that loads in the language phrases, so you can rewrite the screen messages into another language, or just rewrite them. This necessitated changing every print to a screen to call print_phrases(). both.c - read_array(), do_a_phrases(), free_array(), convert_nl() - added. These are used by the load_phrases() routines to read in one array and alloc the memory for each phrases. - print_phrases() vprint_phrases(), str_int() - added. These routines print the phrases out with vars included. - signal_block() - added code to block PAUSESIGNAL. - error_log() - modified to call vprint_phrases() so that phrases print correctly. killfile.c - parse_killfile(), chk_msg_kill() - changed killfile logging from a command time to run time argument, added logyn to Master killfile structure. regex_scan() - fixed bug in handling quote character check_a_group() - changed \r to \n in checking nr of groups check_a_group(), parse_a_file() - added code to handle pathhost, subject, from, and nntp separators in group killfile. rpost.c - main(), scan_args() - added stuff to process language file. - free_phrases() - added, frees up memory from language file load. - load_phrases() - added, loads in the language file. suck.c - main(), scan_args() - added code to pass killog_yn to killfile routines. Added coded to process language file. Added coded to set up PAUSESIGNAL. - get_message_index() - fixed bugs in writing suck.newrc, one where if invalid line, and one where maxread was never getting written back out. Also fixed bug in notting writing out line if error from server. Fixed bug in writing file where the cmd was written instead of line from file. Fixed bug where could write to a NULL fp at end of routine. - get_articles() - rewrote way I calculate width of nritems for screen display, got rid of the log10() call, making the math lib (-lm) unnecessary. - load_phrases() - added, loads in the language file. - free_phrases() - added, frees up memory from language file load. - restart_yn() - fix bug in print of error message which caused it not to show the path of the suck.restart file, but rather show suck.sorted twice. - sighandler() - added code to handle PAUSESIGNAL - pause_signal() - created, swap pause_* with sig_* if we are called by signal handler. This allows the user to slow down suck while we are running. testhost.c - main() - added coded to process language file. - load_phrases() - added, loads in the language file. - free_phrases() - added, frees up memory from language file load. timer.c - TimerFunc() - changed to write to file vice string, so can call print_phrases() 16 Mar 97 - suck-3.4.0 released --Changes to Suck-3.3.1 killfile.c - regex_scan() - fixed bug which caused it to scan the first pattern on line multiple times, instead of scanning each pattern (added startline = tptr at end). rpost.c - main() - added version nr printout to DEBUG3 stuff. suck.c - main() - added version nr printout to DEBUG2 stuff. - restart_yn() - added code to print error message if suck.restart exists, but suck.sorted doesn.t 16 Nov 96 - Suck-3.3.2 released --Changes to Suck-3.3.0 rpost.c - log_fail() - fix bug in sprintf(), remove /. suck.c - get_message_index() - added code so that using a neg nr in sucknewsrc will allow you to download the last X nr of messages, handy for starting a new group. - main() - check for NULLs on debug printout and handle gracefully - scan_args(), get_message_index() - added code to handle msnews.microsoft.com using 224 vice 221 for the return value on the XHDR command, hence the -k (kludge) option. testhost.c - main(), do_a_command() - added code to handle NNTP authorization. sample/get.news* - added simple lock mechanism so two instances of script can't run at same time. 5 Nov 96 - suck-3.3.1 released --Changes to Suck-3.2.2 suck.c - main() - changed call to do_cleanup so that it would be called even if no articles, since this is not an error. - get_articles(), scan_args() - added code to handle the -W wait/pause argument to pause x nr of seconds between y nr of messages. - do_cleanup() - don't generate error if the sorted file doesn't exist. suck_config.h - added ifdef REGEX_H for killfile regex use. killfile.h killfile.c - check_line() - parse_a_file() - free_node() - check_a_group() - added code to do the regex checks on subject, from, path and nntphost. - regex_scan() - regex_check() - created for regex stuff. rpost.c - do_batch() - added code to log failed uploads - log_fail() - created, create a file of failed uploads - do_article() - fixed a bug where if the last line of an article didn't end with a cr nl, then the . to signal eom wasn't recognized by remote server. 7 Oct 96 - Suck-3.3.0 released --Changes to Suck-3.2.1 both.c - changed connect_to_nntphost, so can pass a portnr down, instead of always using the default 119. suck.c rpost.c testhost.c - main(), scan_args() - changed to handle the new option -N, for selecting nnrp port number. killfile.c - free_killfile() - removed lines that were explicitly freeing master.path, that is already handled in free_node(). 21 Sep 96 - Suck-3.2.1 released --Changes to Suck-3.2.0 both.c - build_args() - created, parses a file for args, and builds an argv type array from it. - free_args() - created, frees memory allocated in build_args() chkhistory.c - cmp_msgid() - moved to suckutils.c, since it is also used in chkhistory_db.c. dedupe.c - created, dedupes the linked list of msg-ids. killfile.c - parse_a_file(), check_a_group() - added code to check for NNTP_Posting_Host in headers. killprg.c - chk_msg_kill_fork() - fix bug in check of retval from read of result from child process - killprg_closeit() - add nl to the sprintf so kill command is formatted correctly. rpost.c - made all retvals use ENUM values main() - removed ifdef RPOST_NNRP stuff added -M option processing (mode reader). Rewrote argument handling stuff so can add arg file processing do_article(), do_batch() - fix so if problem with article, it doesn't abort, just goes on to the next article. do_batch() - changed to use new Pargs struct to pass all the options down to it. scan_args() - created, moved argument handling to here. suck.h - Removed #ifdef NNRP made this a run-time option - added more fields to Master (for arg stuff) suck.c - main() - added -M option (mode reader) option. Moved all options to scan_args() so can do the read args from file stuff. This meant adding some fields to master, so could pass back and forth, and re-writing the arg handling stuff. - scan_args() - created, handle args here. - get_announcements() - removed ifdef NNRP stuff added stuff to check do_modereader - do_cleanup() - change FP_DATADIR to FP_TMPDIR on N_NEWRC and N_SORTED so it looks in the right location. - build_list() - deleted, see below. - get_message_index(), allocnode(), do_supplemental() rewritten, now all they do is build a list of message-ids, then the linked list is passed to dedupe_list(). This gets rid of the old way of deduping as building the list, and hopefully will speed up large message downloads. - get_message_index() - Added code to handle maxread parameter in sucknewsrc. Added check for count on group line, so if no messages available we don't even try. suckutils.c - do_lockfile() - change so PMaster is passed so can print to status log. Change print of removing stale lock file from errlog to status log, since this is not technically an error. 13 Sep 96 - Suck-3.2.0 released --Changes to Suck-3.1.0 Makefile.in config.h.in configure.in - various changes so work on more systems *.h - changed #define WORD to #define WORD 1 - various changes so they work with configure both.c killprg.c suck.c suckutils.c - added tests for limits.h - changed pid stuff to assume long vice int pids. This involved changing a couple of switches to if/else, and re-doing printf/scanfs with casts to long. - get_message_index() - added loop at end to write out rest of suck.newrc if the process was aborted (due to loss of pipe or other reasons). both.h - added test for pre-defined TRUE and FALSE - added test for PATH_MAX rpost.c - added -U - P options for NNTP authorization 12 Jul 96 - suck-3.1.0 released. --Changes to Suck-3.0.0 ALL - added const to lots of function calls, and other places. Lots of - touch ups to make code compile without warning messages. ** - changed default to use memmove() vice bcopy() and set up config.h so you would uncomment to use bcopy vice memmove (reverse of old method) ** - changed _POSIX_PATH_MAX to PATH_MAX *.h - all modified so not to get included multiple times, and to stand alone (#ifndef, #define, #endif stuff) *.c - added #include at top for autoconf stuff config.h - moved to suck_config.h so don't conflict with autoconf stuff. - changed CHECK_HISTORY_EXP to CHECK_HISTORY_OLD to make - new version the default. both.c - sgetline() - changed to use memmove vice bcopy - rewrote a bit to not use ENODATA and ETIMEDOUT - signal_block() - added handling for SIGPIPE for killprg suck.c - send_command() - created to handle requests for authorization when commands are sent. Replaces send_a_command(). - main() - added -U, -P option processing, and rewrote to use send_command(). - get_announcements() get_message_index() get_one_article() - rewritten to use send_command() - send_a_command() - removed, replaced by send_command() - main() - added do_cleanup stuff - do_cleanup() - created, cleans up after myself - get_articles() - removed ifp variable and close() not needed. killfile.c - get_one_article_kill() - rewritten to use send_command() - parse_killfile() - get_one_article_kill() - modified to handle call to either chk_msg_kill() or chk_msg_kill_fork() - chk_msg_kill() - fixed bug in counting of commas on group line. - changed functions for change from nrgrps to maxgrps or totgrps, see below. killfile.h - added defines for killprg.c stuff - changed confusing defs of nrgrps in two different structs, now one is maxgrps, one is totgrps. killprg.c - created, this handles forking and calling of separate program - to check messages for killing or keeping rpost.c - do_article() - changed to use memmove vice bcopy - fixed bug in duping beginning of lines dots. - added long line support. - main() dobatch() - add -d option. This option will delete the batch file on successful upload of all messages. chkhistory.c - changed to make the new check_history() routine, made the - old routine optional - chkhistory() (new version) - removed extra fclose(fhist) Makefile - Moved to Makefile.in, rewritten to work with autoconf - added more warnings to defines and to tighten up code, added -ansi and -pedantic, this necessitated bunch of little changes in all files. - added SOCKS support Never released to public, beta testers only. --Changes to Suck-2.6.3 killfile.c - pass_two() - fixed bug in malloc grpname (add +1 to strlen()). - malloc_line() - added +1 to malloc Makefile - fixed problem with older gcc's and linking (move $(LIBS) to end of compile lines). 8 May 1996 - suck 2.6.3 released --Changes to Suck-2.6.2 All files - changed wording so that everything refers to articles vice messages, hopefully to avoid confusion. Makefile - added support for Profiling so can find time wasters. Added support for non standard locations of includes and libs for INN DB stuff. Added support for the separate compiling of chkhistory or chkhistory_db. chkhistory.c - Moved history database stuff to separate file, for easier maintenance. - chkhistory() - there is now an experimental version of the flat file history checker. If you define CHECK_HISTORY_EXP in config.h, you'll get the new version. If not, you'll get the old. chkhistory_db.c - created. - check_history() - fixed bad ifdefs, all should have had USE_ in front of them. - chkhistory() - added missing master->nritems-- killfile.h killfile.c - add NRGRPS param to allow you to prevent SPAM articles suck.h suck.c - added -K option to bypass killfiles, and added -H option to bypass history file check. - build_list() - added prelim 1st test to the dedupe portion so that we don't always call strcmp() in hopes to speed things up. 31 Mar 96 - suck-2.6.2 released --Changes to Suck-2.6.1 killfile.c - corrected len params for hilines and lolines in Params structure. - cleaned up the docs and man pages in suck.1 had HIGHLINES which should have read HILINES - added JDOC/* Japanese docs (Thanks Motoharu ) Makefile - fixed type in DBLIB suck.c - Makefile - main() - fixed version print to handle patch versions (2.6.1) 10 Mar 1996 - suck-2.6.1 released --Changes to Suck-2.6 - moved man pages and sample files to their own directories so that the base dir only contains source code ALL prgs - changed all stderr stuff to go through newly created error_log() - function. This is all part of the process to make these programs - have no messages go to screen, for use in crons, background etc. - Also added -e, -E, -s, and -S options to suck, rpost, and testhost. - This necessitated a change in the command line processing of testhost. - Now testhost requires hostname first. both.c - number() - re wrote to handle leading spaces gracefully - error_log() - created chkhistory.c - Added the stuff to handle DBM, NDBM, DBZ history database schemes. - I hope this stuff works as I have no way to test it. I can't even - compile the NDBM and DBZ stuff, as I don't even have the libs/include - files stuff. - cmp_msgid() - created, used to compare two articles ids IAW rfc 822. config.h - added minor comments re the history stuff and the Makefile. Added stuff - for group kill file processing. killfile.c - parse_killfile(), free_killfile(), chk_msg_kill() - modified - pass_two(), free_node(), check_a_group(), parse_a_file(), strnstr() - created - Major re-write to handle group kill and delete files. - also added quote param, to allow you to specify case-sensitive or - case insensitive matches. - Changed LINES param to HILINES and LOWLINES to match nr of lines above or - below the nr of lines in a article. - main() - commented out perror on no killfile, since this really isn't an error. rpost.c - get_article() - get_batch() - main() - added command line options, see above. - added fptr stuff and passed to subroutines to handle status messages. - added #define RNEWS_NNRP to handle INN servers that need - mode reader to post. suck.c - redid includes, moved various functions to suckutils.c - main() - added -V option to print version number, also added - command line options, see above, and added opening of status - log for silent mode. This changes the handling of the -s option. - get_articles() - removed reduntant killfile tests since that code - is now in killfile.c - main() - rnews_batch() - added -r option, and the ability to build multiple batch - files, with the max size specified on the command line. - get_message_index() - added check for group not found, if got re-write - line to newrc - added sanity check for a low high article nr, - in case remote hosts resets its article nrs. - main(), do_innbatch(), do_rnewsbatch() - added stuff for multiple feeds (postfix) suckutils.c - created, moved various functions not directly related to the getting of the articles from suck.c to here - full_path() - added postfix option so can have multiple feeds. suckutils.h - created testhost.c - added RETVAL enum used thru out program - main() - rewrote command line processing, see above. - added fptr stuff for status messages - added newgroups option. - do_a_command () - added fptr stuff for status messags - added option for args to command to be sent - check_date_format() - created, used to ensure date/time for - newgroups is in the right format. Makefile - tweaks to handle new directory structure and to handle passing version number to compile. Added code to handle DB lib linking and passing args to compile. Added user defines for owner, group, and mode for the executables and the man pages. 19 Feb 1996 - suck-2.6 released --Changes to Suck-2.5.1 get.news.innxmit - added ${SITE} variable, fixed bug on ctlinnd call chkhistory.c - chkhistory() - moved search list stuff inside the else{} routine so - it doesn't get done on a bogus line. suck.c - main() - fixed a couple of printfs still going to stdout vice - where master.msgs points. - get_articles() - added test for master being NULL to free_killfile() call both.c - sgetline() - added eob==start test in case buffer empty, no data for the strstr() test with a dirty buffer - THIS IS THE BUG THAT CAUSED MOST OF THE "No article found" error messages killfile.c - free_killfile() - added test for master being NULL 29 Dec 95 - suck-2.5.1 released --Changes to Suck-2.5 suck.c killfile.c - ALL ROUTINES - Added Master structure, changed basic operation mode. Previously, we read suck.sorted to get each article. This was causing a lot of disk activity. Changed to keep the article list in memory. This included changing the handling of the restart, since we have to read the list into memory on a restart. Also fixed handling of stdout/stderr for status & error messages if in multifile mode or not. suck.c - do_innbatch() - do_rnewsbatch() - changed to use readdir() to build article files - rather than just assuming all articles were downloading - and going one up. This way if one article didn't download - the rest can still be batched up. - full_path() - fixed a bug in the resolving of relative paths. - get_articles() - add check for end of string when parsing for article id. - modified get_message loop to end on signal - send_a_command() - rewrote if to switch to handle UNEXPECTEDANS better - get_a_chunk() - rewrote a bit to speed it up (hopefully) - modified to handle double dots IAW RFC 977 2.4.1 - sighandler() - created - main() - added call to signal() and to signal_block() - added -a options, always_batch; if we download at least one - article batch em up, even if we end on an error - added lockfile stuff - do_lockfile() - created, Hope this is portable - get_message_index() - any nr < 0 will cause us to just update sucknewsrc - with the new high article nr (to start a newgroup) - any line beginning with a # will be skipped - get_messages() - added call to free_killfile() - do_supplemental() - redid status messages a bit, added call to chkhistory() killfile.h - added free_killfile() definition, added to KillStruct killfile.c - get_a_chunk_mem() - modified to handle double dots IAW RFC 977 2.4.1 - parse_killfile() - malloc_line() - created - check_line() - created - chk_msg_kill() - added stuff to search Path, Subject, and From line - and kill em if match - free_killfile() - created Makefile - added mkdir -p to install option, cleaned up a bit both.h - added enum for signal_block routine both.c - sgetline() - #ifdef TIMEOUT around timeval struct in variable declaration area - added calls to signal_block() - signal_block() - created, this is so the recv don't get interrupted by my abort signal - connect_to_nntphost() - added msgs parameter. This way suck can - point msgs to stderr if not in multifile mode config.h - added #define RNEWS for lpost - added #define bcopy() for portability sakes - added #define MYSIGNAL stuff - added #define CHECK_HISTORY stuff lpost.c - moved #define RNEWS to config.h to conform with other prgs get.news - eliminated get.news.innxmit - created, for use by INND - changed to use flush vice reload for ctlinnd get.news.rnews - created, for use by CNEWS testhost.c - added #include "config.h" timer.c - changed message printout to include mins and seconds - added TIMER_TIMEONLY option to just disply time, not bytes timer.h - added to enum chkhistory.h - chkhistory.c - created, allows to check /usr/lib/news/history for any articles this - system already has, and not download them suck.1 rpost.1 lpost.1 testhost.1 - created README README.NEWS - merged, since much of the docs are now in the man pages. 1 Dec 95 - suck-2.5 released --Changes to Suck-2.4.1 config.h - got rid of ##ifdef KILLFILE around #define N_KILLLOG to avoid make errors if KILLFILE is not defined rpost.c - do_batch() - changed do_article() to retval=do_article() so errors in upload got passed back. suck.c - main() get_articles() - added -s option, to force display of the status message for each message to stderr vice stdout. That way if someone is not using batch mode, they can still see those messages. - get_articles() - fixed bug where restart file was always deleted. Oct 8, 1995 - released --Changes to 2.4 Makefile - various tweaks - moved DEBUG stuff to config.h get.news - added comments about where to change for rnews - changed the way I test to see if I am online to using a ping - vice just checking to see if PPP is up and running suck.c - get_announcement() - added #ifndef NNRP if NNTP server is called. NNTP servers don't need 'mode reader' command, so don't send it. - full_path() - modified to allow for command line setting of directory paths; changed variables passed to it. - main() - modified arg checking routine to allow for command line setting of directory paths - get_messages() - changed checkdir() to use full_path() - get_articles() - added #ifdef TIMER stuff - get_articles() - changed memcpy() to bcopy() for portability's sake - full_path() - changed PATH_MAX to POSIX_PATH_MAX for portability's sake - do_innbatch() - do_rnewsbatch() - get_articles() - added logcount, changed printf to make article file names 00XX-1666, where leading zeros are applied - changed B_FALSE, B_INNXMIT, B_RNEWS to BATCH_ to avoid error on Sun compiler - changed informational msgs to print to stdout vice stderr - get_one_article() - created - get_a_chunk() - created - send_a_message() - created - get_articles() - moved all the stuff for one article to get_one_article() - send_a_message() and get_a_chunk() and added killfile - processing stuff and ifdef KILLFILE stuff - get_message_index() main() - moved handling of restart from get_articles() to main() - do_dedupe() - modified - build_list() - created - get_articles() - added supplemental list processing, so user can specify - additional article numbers to download, in addition - to those from sucknewsrc. This is so a user can specify - articles that will NOT be checked against killfile stuff - Now suck.sorted has two fields per line, msgnr and a - one char field that states whether or not I must download - this article. If I must, I will bypass killfile routines. killfile.c - killfile.h - created, contains stuff related to killfile processing suck.h - created, contains functions in suck.c needed by killfile.c stuff config.h - created, moved user changeable #defines to here. Renamed some so that they make more sense. Renamed suck.tmp to suck.newrc to be more descriptive. - added TIMEOUT option - moved DEBUG stuff from Makefile to here both.c - full_path() - moved to suck.c - sgetline() - #ifdef TIMEOUT stuff, to allow us to timeout if no data received from socket in some defined time. - plus some tweaks to speed up the routine - changed informational msgs to print to stdout vice stderr rpost.c - do_batch() - changed behavior if infile doesn't exist. Use to totally bomb the program, Now just displays error msg and goes on to next article - changed informational msgs to print to stdout vice stderr - do_batch() - some INNs put article number in outgoing file - in addition to the file name, so added check for this lpost.c - changed informational msgs to print to stdout vice stderr timer.c - created timer.h - created, contains the timer display and update routines testhost.c- created, allows you to see if host is active and what commands it accepts - added LIST option to get active list 1 Oct 95 - posted --Changes to 2.3B Rewrote code to my code styles, this basically means: no global variables, single exit(), #define anything liable to change, change indentation. both.c - added full_path() so can put files anywhere, just change #defines in both.h. added do_debug(), MyPerror() for easier debugging. sgetline(), total rewrite so less character moving around and handles partial lines, returns with the nl in buffer. both.h - clarifed debugging options. centralized all #defines here for global changes lpost.c - just cosmetic changes, as I hope to phase this out. Makefile - added a couple of comments rpost.c - rewrote to handle batch option added do_article(), process one article added do_batch(), loop thru batch file, process args run filter as needed added do_filter(), basically just fork() and execl() suck.c - main() rewrite to handle args get_articles() rewritten to handle multifile output added get_announcements() to move this out of main() added do_dedupe() so no more system() calls. added allocnode() for use by do_dedupe() added checkdir() to check for and make directory added do_innbatch() to build batch file of article file names needed by innxmit added do_rnewsbatch() to build file formatted for rnews 8 August 95 Suck2.3B released --NEW MAINTAINER 3 July 1995 boby@pixi.com Minor changes so no warning msgs from gcc2.7.0. suck.c - if low=high, never got article, fixed suck.c - changed if lastread in sucknewsrc ==0 no get to == -1 3 July 1995 suck-2.2 released --Changes to 2.2: Sgetline now cares for maxlen which means lines > 1024 bytes will be folded. Lpost got the option -v to print some debugging output. The other changes are only cosmetical :). --Changes to 2.1.1: Despite of the above proclamation i forgot to increase the buffer ;). This is now fixed (hopefully). You see there is definately a need for a new maintainer. --Changes to 2.1: Radically increased buffer (+1 byte) thanks to operator@melchior.frmug.fr.net rpost now more RFC0977 conform. Renamed rpost.sh to blow. Added another local posting script by huber@iamexwi.unibe.ch for inn. --Changes to 2.0: added inews-alike rpost thanks to inspiration from ecarp@netcom.com. --Changes to 1.4: added restart ability thanks to inspiration from David.H.West@um.cc.umich.edu. --Changes to 1.3: fixed numeric ip handling. --Changes to 1.2: added a missing htons() thanks to laurent@brasil.frmug.fr.net added numeric ip address handling. suck-4.3.4/CONTENTS000066400000000000000000000027631333033562000136550ustar00rootroot00000000000000# VARIOUS DOCS CHANGELOG # commentary on what changes I've made to the source CONTENTS # this file README # Descriptions and News you need to read. README.Gui # about my java gui README.Xover # about the new xover killfiles # SCRIPTS TO TIE ALL THIS STUFF TOGETHER sample/get.news.innxmit # sample for INN (innxmit) sample/get.news.rnews # sample for CNEWS (rnews) sample/put.news # used by the get.news scripts # SAMPLE DATAFILES sample/suckkillfile.sample sample/sucknewsrc.sample sample/suckothermsgs.sample sample/killprg_child.c # sample of killprg routine sample/kill_perl.pl # sample of perl embed for killfiles sample/put.news.pl # sample of perl embed for rpost sample/post_filter.pl # sample of pre-post filter # MAN PAGES man/suck.1 man/lpost.1 man/rpost.1 man/testhost.1 # JAVA RELATED FILES java/Suck.java # a Java gui for downloading news java/Suck.class java/SuckMenu.class java/SuckUpdate.class # PERL RELATED FILES perl/header.pl # a Perl/Tk gui to display articles/header # SOURCE FILES active.c active.h batch.c batch.h both.c both.h both_phrases.c chkhistory.c chkhistory.h chkhistory_db.c db.c db.h dedupe.c dedupe.h killfile.c killfile.h killprg.c lmove.c lmove_phrases.c lpost.c phrases.h read_db.c rpost.c rpost_phrases.c ssort.c ssort.h suck.c suck.h suck_config.h suck_phrases.c suckutils.c suckutils.h testhost.c test_phrases.c timer.c timer.h xover.c xover.h # STUFF FOR AUTO-CONFIGURING Makefile.in config.h.in configure configure.in install-sh stamp-h suck.spec suck-4.3.4/Makefile.in000066400000000000000000000236341333033562000145420ustar00rootroot00000000000000SHELL=/bin/sh .SUFFIXES: .c .0 ############################################################################ # History File Configuration Stuff ############################################################################ # Step 1: Define ONE of the CHKHISTORY CHKHISTORY=@HISTORY@ #CHKHISTORY=chkhistory.o # use flat file history routine #CHKHISTORY=chkhistory_db.o # use DB history routine, if you use # DBM, GDBM, NDBM, or DBZ # in order to use DBZ with INN you will need # the full compiled source code, especially # inn/lib/libinn.so & inn/include/dbz.h # If you are using flat file, you can skip the rest, they don't matter # Step 2: Define ONE of the Database Types DB_TYPE=@DB_TYPE@ #DB_TYPE=-DUSE_DBM #DB_TYPE=-DUSE_GDBM #DB_TYPE=-DUSE_NDBM #DB_TYPE=-DUSE_DBZ # for inn-1.X.X users #DB_TYPE=-DUSE_INN2 # for inn-2.1-2.2 users #DB_TYPE=-DUSE_INN23 # for inn-2.3.X users # Step 3: Define ONE Library that contains the functions # If you are using INN-2.X, and the compiler complains # about Undefined Symbols QIOclose QIOopen QIOread # Use the DB_LIB that includes -lstorage DB_LIB=@DB_LIB@ #DB_LIB=-ldbm #DB_LIB=-lgdbm #DB_LIB=-lndbm #DB_LIB=-ldbz #DB_LIB=-linn -lstorage # see note above #DB_LIB=-linn # INN puts DBZ code in here # Step 4: IF your DB Library/Includes are in a non-standard place, # define These #DB_INC_LOC=-I/usr/src/inn-2.3.4/include #DB_LIB_LOC=-L/usr/src/inn-2.3.4/lib # Step 5: If you want to use embedded PERL functions as kill routines # define these. # PERL_CORE needs to point to where perl.h and libperl.a lives. # PERL_DEFS might need to be changed, depending on how you compiled # your perl. These defaults should be good for most people. # If you get errors about missing libs, add em to the PERL_LIB define. # If your compile fails with refs to missing symbols like pow() and floor(), # add -lm to the PERL_LIB line. # The rest of these should be left alone (except to remove the #). # If you're using perl5.004 or older, add "-DOLD_PERL to PERL_DEFS PERL_LIB=@PERL_LIB@ PERL_DEFS=@PERL_DEFS@ PERL_INC_LOC=@PERL_INC_LOC@ PERL_LIB_LOC=@PERL_LIB_LOC@ #PERL_CORE=/usr/lib/perl5/5.8.0/i386-linux-thread-mult/CORE #PERL_LIB=-lperl -lcrypt PERL_DEFS=-D_REENTRANT -D_GNU_SOURCE #PERL_INC_LOC=-I$(PERL_CORE) #PERL_LIB_LOC=-L$(PERL_CORE) # Step 6: If compiling for OS/2 uncomment the following #OS2_LD=-Zexe #OS2_DEFS=-DEMX # Step 7: If compiling with SSL uncomment the following SSL_LIB=@SSL_LIB@ SSL_DEFS=@SSL_DEFS@ #SSL_LIB=-lssl -lcrypto #SSL_DEFS=-I/usr/local/ssl/include -DHAVE_LIBSSL #SSL_LIB_LOC=-L/usr/local/ssl/lib ############################################################################ # COMPILE flags for GCC ############################################################################ GCC_FLAGS= DBZ_GCC_FLAGS= ############################################################################ # YOU SHOULD NOT NEED TO TOUCH BELOW HERE ############################################################################ # my compile stuff ############################################################################ MY_GCC_FLAGS = -Wall -Wpointer-arith -Wcast-align -Wcast-qual \ -Wshadow -Waggregate-return -Wmissing-prototypes -Wnested-externs \ -Wstrict-prototypes -Wwrite-strings -Wmissing-declarations MY_DBZ_GCC_FLAGS = -Wall -Wpointer-arith -Wcast-align -Wcast-qual \ -Waggregate-return -Wmissing-prototypes -Wnested-externs -Wwrite-strings \ -Wmissing-declarations ############################################################################ #GCC_FLAGS= $(MY_GCC_FLAGS) #DBZ_GCC_FLAGS= $(MY_DBZ_GCC_FLAGS) ############################################################################## # Ultrix 2.2 make doesn't expand the value of VPATH. VPATH = @srcdir@ # This must repeat the value, because configure will remove `VPATH = .'. srcdir = @srcdir@ # Suck Makefile PKGNAME=suck VERSION_MAJOR=4 VERSION_MINOR=3 VERSION_PATCH=4 VERSION=$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH) CC = @CC@ @SET_MAKE@ CFLAGS = @CFLAGS@ CPPFLAGS = -I. -I$(srcdir) @CPPFLAGS@ LDFLAGS = @LDFLAGS@ $(DMALLOC_LD) $(OS2_LD) DEFS = @DEFS@ -DSUCK_VERSION=\"$(VERSION)\" $(DMALLOC_DEFS) $(OS2_DEFS) $(DMALLOC_INC) LIBS = @LIBS@ $(DMALLOC_LIB) prefix = @prefix@ exec_prefix = @exec_prefix@ bindir = @bindir@ manext = 1 mandir = @mandir@/man$(manext) srcdir = @srcdir@ MAN = $(srcdir)/man SMAN= $(srcdir)/Spanish.docs INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ SPECVERSION = `sed -n 's/^%define version //p' suck.spec` #List of .o files suck needs to compile: suckobjs = both.o both_phrases.o suck.o suckutils.o killfile.o timer.o killprg.o \ dedupe.o suck_phrases.o active.o batch.o xover.o ssort.o $(CHKHISTORY) \ db.o rpostobjs = both.o both_phrases.o rpost.o rpost_phrases.o testhobjs = both.o both_phrases.o testhost.o test_phrases.o makepobjs = makephrases.o suck_phrases.o rpost_phrases.o test_phrases.o \ both_phrases.o lmove_phrases.o lmoveobjs = both.o both_phrases.o lmove.o lmove_phrases.o all: phrases.h suck rpost testhost lmove @echo make done suck: phrases.h $(suckobjs) $(CC) $(LDFLAGS) $(DB_LIB_LOC) $(PERL_LIB_LOC) $(SSL_LIB_LOC) -o suck $(suckobjs) $(LIBS) $(DB_LIB) $(PERL_LIB) $(SSL_LIB) lmove: phrases.h $(lmoveobjs) $(CC) $(LDFLAGS) $(SSL_LIB_LOC) -o lmove $(lmoveobjs) $(LIBS) $(SSL_LIB) lpost: lpost.o $(CC) $(LDFLAGS) -o lpost lpost.o $(LIBS) readdb: phrases.h read_db.o $(CC) $(LDFLAGS) -o read_db read_db.o $(LIBS) rpost: phrases.h $(rpostobjs) $(CC) $(LDFLAGS) $(PERL_LIB_LOC) $(SSL_LIB_LOC) -o rpost $(rpostobjs) $(LIBS) $(PERL_LIB) $(SSL_LIB) testhost: phrases.h $(testhobjs) $(CC) $(LDFLAGS) $(SSL_LIB_LOC) -o testhost $(testhobjs) $(LIBS) $(SSL_LIB) makephrases: $(makepobjs) $(CC) $(LDFLAGS) -o makephrases $(makepobjs) $(LIBS) phrases: makephrases ./makephrases phrases.engl phrases.h: makephrases ./makephrases phrases.h install: install_bin install_man #install_Spanish: install_bin install_sman install_bin: suck rpost testhost lmove - mkdir -p $(DESTDIR)$(bindir) $(INSTALL_PROGRAM) suck $(DESTDIR)$(bindir)/suck $(INSTALL_PROGRAM) rpost $(DESTDIR)$(bindir)/rpost $(INSTALL_PROGRAM) testhost $(DESTDIR)$(bindir)/testhost $(INSTALL_PROGRAM) lmove $(DESTDIR)$(bindir)/lmove install_man: $(MAN)/suck.1 $(MAN)/rpost.1 $(MAN)/testhost.1 $(MAN)/lmove.1 - mkdir -p $(DESTDIR)$(mandir) - rm -f $(mandir)/lpost.$(manext) $(INSTALL_DATA) $(MAN)/suck.1 $(DESTDIR)$(mandir)/suck.$(manext) $(INSTALL_DATA) $(MAN)/rpost.1 $(DESTDIR)$(mandir)/rpost.$(manext) $(INSTALL_DATA) $(MAN)/testhost.1 $(DESTDIR)$(mandir)/testhost.$(manext) $(INSTALL_DATA) $(MAN)/lmove.1 $(DESTDIR)$(mandir)/lmove.$(manext) #install_sman: $(SMAN)/suck.1 $(SMAN)/rpost.1 $(SMAN)/testhost.1 $(SMAN)/lpost.1 # $(MAKE) -C $(SMAN) install_lpost: lpost $(MAN)/lpost.1 $(INSTALL_PROGRAM) lpost $(DESTDIR)$(bindir)/lpost $(INSTALL_DATA) $(MAN)/lpost.1 $(DESTDIR)$(mandir)/lpost.$(manext) installall: install install_lpost clean: rm -f `find . \( -name "*.o" -o -name "*[~%]" \) -print` chkhistory_db.o: chkhistory_db.c $(CC) -c $(DB_INC_LOC) $(CFLAGS) $(DBZ_GCC_FLAGS) $(CPPFLAGS) $(DEFS) $(DB_TYPE) $< killfile.o: killfile.c $(CC) -c $(CFLAGS) $(DBZ_GCC_FLAGS) $(CPPFLAGS) $(DEFS) $(PERL_DEFS) $(PERL_INC_LOC) $< killprg.o: killprg.c $(CC) -c $(CFLAGS) $(DBZ_GCC_FLAGS) $(CPPFLAGS) $(DEFS) $(PERL_DEFS) $(PERL_INC_LOC) $< xover.o: xover.c $(CC) -c $(CFLAGS) $(DBZ_GCC_FLAGS) $(CPPFLAGS) $(DEFS) $(PERL_DEFS) $(PERL_INC_LOC) $< rpost.o: rpost.c $(CC) -c $(CFLAGS) $(SSL_DEFS) $(DBZ_GCC_FLAGS) $(CPPFLAGS) $(DEFS) $(PERL_DEFS) $(PERL_INC_LOC) $< .c.o: $(CC) -c $(CFLAGS) $(SSL_DEFS) $(GCC_FLAGS) $(CPPFLAGS) $(DEFS) $< #--------------------------------------------------------------------------- # stuff to make sure configure is up to date (I hope) #--------------------------------------------------------------------------- ${srcdir}/configure: configure.ac cd ${srcdir} && autoconf # autoheader might not change config.h.in, so touch a stamp file ${srcdir}/config.h.in: stamp-h.in ${srcdir}/stamp-h.in: configure.ac cd ${srcdir} && autoheader echo timestamp > ${srcdir}/stamp-h.in config.h: stamp-h stamp-h: config.h.in config.status ./config.status Makefile: Makefile.in config.status ./config.status config.status: configure ./config.status --recheck #--------------------------------------------------------------------------- # stuff for me compiling/debugging this sucker #--------------------------------------------------------------------------- debug: moreclean make CFLAGS="-g" LDFLAGS="-g" make readdb CFLAGS=-g LDFLAGS="-g" debug_both: moreclean make CFLAGS="-g -DDEBUG1=1" LDFLAGS="-g" profile: moreclean make CFLAGS="-pg" LDFLAGS="-pg" cleanrun: rm -rf Msgs rm -f suck.errlog suck.status suck.newrc suck.index suck.sorted suck.post \ suck.restart suck.lock suck.killlog debug.* gmon.out batch *.tmp core suck.db \ dmalloc_logfile moreclean: clean cleanrun rm -f suck rpost testhost lpost makephrases lmove read_db distclean: clean cleanrun cleanconfig moreclean rm -rf test rm -f sucknewsrc sucknewsrc.old suckkillfile suckothermsgs batch \ phrases.h phrases.engl active active.old active-ignore suckxover suck.db.out cleanconfig: rm -f Makefile Spanish.docs/Makefile config.h config.cache config.status config.log makedist: makerpm echo "Done" maketar: distclean # make tar tar -czv -f ../$(PKGNAME)-$(VERSION).tar.gz -C .. suck-$(VERSION) makerpm: maketar # now make RPMS if [ "${SPECVERSION}" != "${VERSION}" ] ; then \ echo Mismatch: Makefile version ${VERSION} and .spec file version ${SPECVERSION} ; \ exit 1 ; \ fi #pwd rpmbuild -ta /home/bobyetman/${PKGNAME}-${VERSION}.tar.gz # move em to my home dir and clean up mv /usr/src/redhat/RPMS/i386/*.rpm ~ mv /usr/src/redhat/SRPMS/*.rpm ~ rm -rf /usr/src/redhat/BUILD/* rm -rf /tmp/suck* # now generate the .sig files #pgps -b ~/suck*.rpm ~/suck*.tar.gz suck-4.3.4/README000066400000000000000000000120651333033562000133510ustar00rootroot00000000000000PREFACE ======= Original Author - Tim Smith (address unknown) Maintainers: March 1995 - Sven Goldt (goldt@math.tu-berlin.de) July 1995 - Robert A. Yetman (bobyetman@sucknews.org) September 2017 - Michael Vetter (jubalh@iodoru.org) Current Maintainer - jubalh@iodoru.org LOCATION ======== https://github.com/lazarus-pkgs/suck INTRODUCTION ============ The primary use for suck is to feed a local NNTP server, without the remote NNTP feeding you articles. It is designed for a small, partial news feed. It is NOT designed to feed 10,000 groups and 3 Gigs of articles a day. This package contains software for copying news from an NNTP server to your local machine, and copying replies back up to an NNTP server. It works with most standard NNTP servers, including INN, CNEWS, DNEWS, and typhoon. The suck/rpost combination allows you to run your own site, controlling where you get your news, and where you post outgoing articles. Suck/rpost use only standard NNTP commands that are used by your favorite news reader (tin, xvnews, strn) such as POST and ARTICLE. If you can use tin or xvnews against a NNTP site, than you can use Suck/Rpost. suck Pull a small newsfeed from an NNTP server lpost Gives one article fetched by suck to the local server. rpost Posts article(s) to a remote NNTP server testhost Check to see what commands your host recognizes or get the active or new list. lmove put articles in news/group/number format. LICENSING INFO ============== Suck is Public Domain, feel free to make any changes you want, just don't blame me when they break. NOTES: ====== Suck will not work with obsolete NNTP servers that can't handle the xhdr command. This code assumes an ANSI-compliant compiler, it will NOT work with old compilers which don't accept function prototypes. The Makefile assumes you are using GNU make, other makes may or may not work. If your remote INN server slows drastically after 100 messages are downloaded, and they are using INN 1.5.1 or newer, chances are they compiled INN with "LIKE_PULLERS" set to DONT, which causes INN to put a small sleep before each message. Talk to the SA for the system and see if they'll compile with "DO". Chances are they didn't even know this option existed. An interesting program for processing the killfile log and forcing suck to download some of the articles in it can be found at: http://ourworld.compuserve.com/homepages/jvanerp/suckmore.html Is suck Y2K compliant? As far as I can make it yes. The only program that uses dates is testhost, and the date is sent to the remote NNTP host for the NEWGRP command. I don't do any date calculations. Once servers take an 8 digit date (right now its YYMMDD), I'll update testhost. HOW-TO-USE ========== 0. Run ./configure --help to see the options, especially the ones listed last (--with-inn-lib && with-inn-include) 1. run ./configure If it can't find your Inn libraries/includes, make sure you use the --with-inn-lib=path & --with-inn-include=path options 2. Check over the Makefile, make sure it found everything, if not fix it. If your flatfile history file is not at /var/lib/news/history, you'll need to use the -HF option, to tell suck where it is. If you're using Inn 2.3 or newer, make sure the Makefile knows where your libinn.a and includes are, otherwise, the dedupe function won't work. See step #4. Typically, these are in the source tree. If you don't have the source, get it, see below. 3. If you're using SSL edit the Makefile step #nr7. 4. Make it. (make , make install) If you are having problems compiling suck, look at the Makefile, there are comments in there for the various common problems I see. 5. READ THE MAN PAGES. 6. Create a sucknewsrc - which groups to download (see suck man pg). If you have INND/CNEWS/DNEWS/PNEWS already running, then just use 'suck remotehost -A -hl localhost', and the sucknewsrc will be built for you. BUT, before you do this, create a active.ignore file, listing all the groups on the local host that you don't want downloaded from the remote host (see man page for sample). 7. Take a look at the sample directory. There are two shell scripts in there that show the whole scheme from how to download news (with suck), and how to upload news (with rpost). Use get.news.inn if you have INN running, as it knows how to gracefully get the outgoing article listing. get.news.generic is for everyone else. WARNING: These scripts need editing before you can use them. You must change the various variables at the top of the scripts to ensure they point to the right files/directories. If you're using Inn 2.3.X with CNFS, you'll need to use the 'put.news.sm*' scripts, to deal with the tokens that Inn puts in the outgoing file instead of the article number. WHERE TO GET STUFF ================== inn: anonymous ftp from ftp.isc.org /isc/inn perl: http://www.perl.com/CPAN/src/latest.tar.gz anonymous ftp from prep.ai.mit.edu /gnu Perl/Tk: http://www.perl.com/CPAN/modules/by-module/Tk/Tk8XX.XXX.tar.gz SSL: http://www.openssl.org/ FUTURE PLANS. ======================== suck-4.3.4/README.Gui000066400000000000000000000004121333033562000140650ustar00rootroot00000000000000There are two GUIs now included with Suck. 1. perl/header.pl - This is a perl/Tk program to view articles headers and add them to suckothermsgs. 2. java/Suck.class - This is a Java GUI for running a suck script. See the README in each directory for more details. suck-4.3.4/README.Xover000066400000000000000000000041741333033562000144550ustar00rootroot00000000000000Starting wich Suck-3.10.0, suck now supports a second type of killfiles. The original killfiles have not changed, and are still fully supported. In addition to the original killfiles, suck now supports killfiles that are processed by on data received from the XOVER command, not from the articles themselves. The master killfile for this set is called "suckxover". Using the normal killfiles, suck would use the command XHDR to get the MessageID numbers for each group, then download each MessageID one at a time and check it against the killfiles, then go onto the next message. To check against the killfiles, I'd first download the HEADER, check it, then if I needed to, download the BODY. Because of this, I can't buffer commands and articles, so suck isn't very efficient. This changes under the new killfiles. With these, suck now uses the XOVER command to get not only MessageIDs, but also the From:, Subject:, References:, Lines:, and ByteCount at the same time. I can use this information to check against killfiles without downloading the header or the body of the article. To see which header fields your remote server supports via XOVER, run testhost remosthost -o. If you need the -M command with suck, you'll probably need to run it as testhost remotehost -o -M. There are a few advantages to this approach. The first is I can get the XOVER information for an entire group at a time, rather than one message at a time. This should be more efficient on the server. Plus, if you are using the BODYSIZE commands in killfiles, I don't have to download any of the message to kill it, saving time and bandwidth. In addition, I don't have to download headers and bodies separate, and piece them back together. Also, when downloading messages, if there are no killfiles, there is pipelining code to make more efficient use of bandwidth. Basically, I'm sending article commands 1 ahead of the one I'm receiving, so there should always be one waiting for me. If you don't use NRGROUPS, HEADER, BODY, and stick with the headers produced by the XOVER command, you should get quite a performance boost, just by renaming suckkillfile to suckxover. suck-4.3.4/active.c000066400000000000000000000323431333033562000141110ustar00rootroot00000000000000#include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_REGEX_H #include #endif #ifdef DMALLOC #include #endif #include "suck_config.h" #include "both.h" #include "suck.h" #include "phrases.h" #include "timer.h" #include "suckutils.h" #include "active.h" typedef struct Dactive { char *group; int high; int done; /* have we gotten the msgids ? */ int postyn; struct Dactive *next; } Active, *Pactive; typedef struct Dignore { char *group; struct Dignore *next; #ifdef HAVE_REGEX_H regex_t match; int use_regex; #endif } Ignore, *Pignore; /* local function prototypes */ int add_to_list(Pactive *, const char *, Pignore, int); int get_msgids(PMaster, Pactive); Pignore read_ignore(PMaster); void do_free(Pactive, Pignore); Pactive nntp_active(PMaster, Pignore); Pactive read_active(PMaster, Pignore); /*-------------------------------------------------------------------------*/ int get_message_index_active(PMaster master) { int retval = RETVAL_OK; Pactive listhead = NULL; Pignore ignorehead = NULL; TimerFunc(TIMER_START, 0, NULL); /* first get our groups to ignore */ ignorehead = read_ignore(master); /* Now try to get the active list */ if(master->do_active == TRUE) { listhead = nntp_active(master, ignorehead); } if(listhead == NULL && master->activefile != NULL) { /* can we get active list from local file */ listhead = read_active(master, ignorehead); } /* if it takes a while to read the active the other end */ /* might have timed out, hence this option */ if(master->conn_active == TRUE) { retval = do_connect(master, CONNECT_AGAIN); } /* now get the MsgIds */ if(retval == RETVAL_OK && listhead != NULL) { retval = get_msgids(master, listhead); do_free(listhead, ignorehead); } else { retval = RETVAL_ERROR; } TimerFunc(TIMER_TIMEONLY, 0, master->msgs); if(retval == RETVAL_ERROR){ retval = get_message_index(master); } else { print_phrases(master->msgs, active_phrases[5], str_int(master->nritems), NULL); } return retval; } /*------------------------------------------------------------------------*/ Pactive nntp_active(PMaster master, Pignore ignorehead) { int localfd, nr; char *resp; Pactive listhead = NULL; int retval = RETVAL_OK; /* get active file from nntp host */ if((localfd = connect_local(master)) < 0 ) { error_log(ERRLOG_REPORT, active_phrases[0], NULL); } else { /* now get the list of groups */ print_phrases(master->msgs, active_phrases[2], master->localhost, NULL); if(master->debug == TRUE) { do_debug("Sending command: LIST\n"); } sputline(localfd, "LIST\r\n", master->local_ssl, master->local_ssl_struct); if(sgetline(localfd, &resp, master->local_ssl, master->local_ssl_struct) >= 0) { if(master->debug == TRUE) { do_debug("got answer: %s", resp); } number(resp, &nr); if(nr == 215) { /* now we can get the list */ do { if(sgetline(localfd, &resp, master->local_ssl, master->local_ssl_struct) > 0) { if(master->debug == TRUE) { do_debug("Got groupline: %s", resp); } if(resp[0] != '.' ) { retval = add_to_list(&listhead, resp, ignorehead,master->debug); } } else { retval = RETVAL_ERROR; } } while(resp[0] != '.' && retval == RETVAL_OK); if(retval != RETVAL_OK) { do_free(listhead, NULL); listhead = NULL; } } } disconnect_from_nntphost(localfd, master->local_ssl, &master->ssl_struct); } return listhead; } /*------------------------------------------------------------------------*/ Pactive read_active(PMaster master, Pignore ignorehead) { /* read active list from file */ Pactive listhead = NULL; FILE *fpi; char linein[MAXLINLEN]; int retval = RETVAL_OK; if(master->debug == TRUE) { do_debug("Opening Active file: %s\n", master->activefile); } if((fpi = fopen(master->activefile, "r")) == NULL) { error_log(ERRLOG_REPORT, active_phrases[11], master->activefile, NULL); } else { while(fgets(linein, MAXLINLEN, fpi) != NULL && retval == RETVAL_OK) { if(master->debug == TRUE) { do_debug("Got line: %s", linein); } retval = add_to_list(&listhead, linein, ignorehead, master->debug); } fclose(fpi); if(retval != RETVAL_OK) { do_free(listhead, NULL); listhead = NULL; } } return listhead; } /*------------------------------------------------------------------------*/ int connect_local(PMaster master) { /* connect to localhost NNTP server */ int fd; char *inbuf; unsigned int port; port = (master->local_ssl == TRUE) ? LOCAL_SSL_PORT : LOCAL_PORT; if(master->debug == TRUE) { do_debug("Connecting to %s on port %d\n", master->localhost, port); } if((fd = connect_to_nntphost(master->localhost, NULL, 0, NULL, port, master->local_ssl, &master->local_ssl_struct)) >= 0) { /* get the announcement line */ if(sgetline(fd, &inbuf, master->local_ssl, master->local_ssl_struct) < 0) { close(fd); fd = -1; } else if(master->debug == TRUE) { do_debug("Got: %s", inbuf); } } return fd; } /*----------------------------------------------------------------------------*/ int add_to_list(Pactive *head, const char *groupline, Pignore ignorehead, int debug) { /* add one group to group list */ Pactive temp, tptr; Pignore pignore; int len, retval= RETVAL_OK; char postyn; #ifdef HAVE_REGEX_H int reg_match; #endif len = 0; /* get length of group name */ while(groupline[len] != ' ' && groupline[len] != '\0') { len++; } if((temp = malloc(sizeof(Active))) == NULL) { error_log(ERRLOG_REPORT, active_phrases[1], NULL); retval = RETVAL_ERROR; } else if((temp->group = malloc(len+1)) == NULL) { error_log(ERRLOG_REPORT, active_phrases[1], NULL); retval = RETVAL_ERROR; } else { /* now initialize the sucker and add it to the list*/ temp->next = NULL; temp->high = 0; temp->done = FALSE; strncpy(temp->group, groupline, len); temp->group[len] = '\0'; /* NULL terminate it */ sscanf(groupline, "%*s%*ld%*ld%c", &postyn); temp->postyn = (postyn == 'n') ? FALSE : TRUE; /* now check to see if we ignore this group, if so, don't add to list */ /* we have to wait until here, because only now do we have the group */ /* name in a separate field we can compare to the ignore list */ pignore = ignorehead; #ifndef HAVE_REGEX_H while(pignore != NULL && strcmp(pignore->group, temp->group) != 0) { pignore = pignore->next; } #else reg_match = FALSE; while(pignore != NULL && reg_match == FALSE) { if(pignore->use_regex == FALSE) { if(strcmp(pignore->group, temp->group) == 0) { reg_match = TRUE; } else { pignore = pignore->next; } } else { if(regexec(&(pignore->match),temp->group, 0, NULL, 0) == 0) { reg_match = TRUE; } else { pignore = pignore->next; } } } #endif if(debug == TRUE ) { if(pignore == NULL) { do_debug("Adding to active list - %s\n",temp->group); } else { do_debug("Ignoring Group %s - match on %s\n",temp->group, pignore->group); } } if(pignore == NULL) { /* we didn't match, add to list */ if(*head == NULL) { /* head node */ *head = temp; } else { /* find end of list */ tptr = *head; while(tptr->next != NULL) { tptr = tptr->next; } tptr->next = temp; } } } return retval; } /*---------------------------------------------------------------------------------*/ void do_free(Pactive head, Pignore ihead) { /* free both linked lists and any alloced strings */ Pactive temp; Pignore itemp; while(head != NULL) { if(head->group != NULL) { free(head->group); } temp=head->next; free(head); head = temp; } while(ihead != NULL) { if(ihead->group != NULL) { free(ihead->group); } itemp = ihead->next; free(ihead); ihead = itemp; } } /*-------------------------------------------------------------------------------*/ int get_msgids(PMaster master, Pactive head) { /* read in the sucknewsrc, check to see if group is in active list */ /* then download msgids and write it out to the new.sucknewsrc */ FILE *oldrc, *newrc; int retval = RETVAL_OK, nrread, maxread; long lastread; char buf[MAXLINLEN+1], group[512], *ptr; Pactive plist; oldrc = newrc = NULL; if((newrc = fopen(full_path(FP_GET, FP_TMPDIR, N_NEWRC), "w" )) == NULL) { MyPerror(full_path(FP_GET, FP_TMPDIR, N_NEWRC)); retval = RETVAL_ERROR; } if((oldrc = fopen(full_path(FP_GET, FP_DATADIR, N_OLDRC), "r" )) == NULL) { /* this isn't actually an error, since we can create it */ print_phrases(master->msgs, active_phrases[6], NULL); } else { print_phrases(master->msgs, active_phrases[9], NULL); while(retval == RETVAL_OK && fgets(buf, MAXLINLEN-1, oldrc) != NULL) { ptr = buf; if(*ptr == SUCKNEWSRC_COMMENT_CHAR) { /* skip any white space before newsgroup name */ while(! isalpha(*ptr)) { ptr++; } } maxread = -1; /* just in case */ nrread = sscanf(ptr, "%s %ld %d\n", group, &lastread, &maxread); if(nrread < 2 || nrread > 3) { /* totally ignore any bogus lines */ print_phrases(master->msgs, active_phrases[3], buf, NULL); } else { /* now find if group is still in active or not */ plist = head; while( plist != NULL && strcmp(plist->group, group) != 0) { plist = plist->next; } if(plist == NULL) { print_phrases(master->msgs, active_phrases[4], buf, NULL); } else { /* valid group, lets get em */ if(plist->postyn == FALSE) { /* we can't post, comment the line out */ fprintf(newrc, "# %s %ld", group, lastread); if(maxread >= 0) { fprintf(newrc, " %d", maxread); } fputc('\n', newrc); } else if(maxread == 0) { /* just rewrite the line */ fprintf(newrc,"%s %ld %d\n", group, lastread, maxread); } else { retval = do_one_group(master, buf, group, newrc, lastread, maxread); plist->done = TRUE; } } } } /* this is in case we had to abort the above while loop (due to loss of pipe to server) */ /* and we hadn't finished writing out the suck.newrc, this finishes it up. */ if(retval != RETVAL_OK) { do { fputs(buf, newrc); } while(fgets(buf, MAXLINLEN-1, oldrc) != NULL); } fclose(oldrc); } if(retval == RETVAL_OK) { /* okay add any new groups from active that weren't already in sucknewsrc */ plist = head; if(plist != NULL) { print_phrases(master->msgs, active_phrases[8], NULL); } while( plist != NULL) { if(plist->done == FALSE) { /* create a line for newsrc file */ lastread = master->active_lastread; maxread = 0; sprintf(buf,"%s %ld\n", plist->group, lastread); print_phrases(master->msgs, active_phrases[10], plist->group, NULL); if(plist->postyn == FALSE) { /* we can't post, comment the line out */ fprintf(newrc, "# %s", buf); } else { retval = do_one_group(master, buf, plist->group, newrc, lastread, maxread); plist->done = TRUE; } } plist = plist->next; } } if(newrc != NULL) { fclose(newrc); } return retval; } /*--------------------------------------------------------------------------------------------------*/ Pignore read_ignore(PMaster master) { /* read in ignore file, and build list of groups to ignore */ FILE *fpi; Pignore head, temp, last; char buf[MAXLINLEN+1], *ptr; int errflag = FALSE; #ifdef HAVE_REGEX_H int err; char errmsg[256]; int i; #endif head = last = NULL; /* first check if one with postfix is present, if not, use non postfix version */ fpi = fopen(full_path(FP_GET, FP_DATADIR, N_ACTIVE_IGNORE), "r"); if(fpi == NULL) { fpi = fopen(full_path(FP_GET_NOPOSTFIX, FP_DATADIR, N_ACTIVE_IGNORE), "r"); } if(fpi != NULL) { while(fgets(buf, MAXLINLEN, fpi) != NULL) { /* strip off any trailing spaces from group name */ ptr = buf; while(!isspace(*ptr)) { ptr++; } *ptr = '\0'; if((temp = malloc(sizeof(Ignore))) == NULL) { error_log(ERRLOG_REPORT, active_phrases[7], NULL); errflag = TRUE; } else if((temp->group = malloc(strlen(buf)+1)) == NULL) { error_log(ERRLOG_REPORT, active_phrases[7], NULL); errflag = TRUE; } else { if(master->debug == TRUE) { do_debug("Ignoring group %s\n", buf); } /* add to list */ strcpy(temp->group, buf); temp->next = NULL; if(head == NULL) { head = temp; } else { last->next = temp; } last = temp; #ifdef HAVE_REGEX_H /* first add ^ and $ so that we don't match */ /* partials unless they have wild cards */ if(buf[0] != '^') { buf[0] = '^'; strcpy(&buf[1], temp->group); } else { strcpy(buf,temp->group); } i = strlen(buf); if(buf[i-1] != '$') { buf[i] = '$'; buf[i+1] = '\0'; } /* now regcomp it for later comparision */ temp->use_regex = TRUE; if((err = regcomp(&(temp->match), buf, REG_NOSUB | REG_ICASE | REG_EXTENDED)) != 0) { regerror(err, &(temp->match), errmsg, sizeof(errmsg)); error_log(ERRLOG_REPORT, active_phrases[12], buf, errmsg, NULL); temp->use_regex = FALSE; } #endif } } fclose(fpi); } if(errflag == TRUE) { while(head != NULL) { if(head->group != NULL) { free(head->group); } temp = head->next; free(head); head = temp; } } return head; } suck-4.3.4/active.h000066400000000000000000000002211333033562000141040ustar00rootroot00000000000000#ifndef _SUCK_ACTIVE_H #define _SUCK_ACTIVE_H 1 int get_message_index_active(PMaster); int connect_local(PMaster); #endif /* _SUCK_ACTIVE_H */ suck-4.3.4/batch.c000066400000000000000000000262601333033562000137200ustar00rootroot00000000000000#include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYS_WAIT_H #include #endif #include #include #include #ifdef HAVE_DIRENT_H # include #else # ifdef HAVE_SYS_NDIR_H # include # endif # ifdef HAVE_SYS_DIR_H # include # endif #endif #ifdef DMALLOC #include #endif #include "suck_config.h" #include "suck.h" #include "both.h" #include "batch.h" #include "suckutils.h" #include "phrases.h" #include "active.h" #include "timer.h" /*--------------------------------------------------------*/ /* function prototypes */ int post_one_msg(PMaster, int, char *); /*----------------------------------------------------------*/ int do_innbatch(PMaster master) { /* build batch file that contains article listing */ /* needed by innxmit */ /* this is done by searching thru MSGDIR to find files */ /* which match our naming convention */ int i, retval = RETVAL_OK; FILE *fptr; const char *tmp; DIR *dptr; struct dirent *entry; print_phrases(master->msgs, batch_phrases[3], NULL); if((fptr = fopen(master->batchfile, "w")) == NULL) { MyPerror(master->batchfile); retval = RETVAL_ERROR; } else if((dptr = opendir(full_path(FP_GET, FP_MSGDIR, ""))) == NULL) { MyPerror(full_path(FP_GET, FP_MSGDIR, "")); retval = RETVAL_ERROR; fclose(fptr); } else { tmp = full_path(FP_GET_POSTFIX, 0, ""); /* this will be string we search for */ /* look for entries which have our postfix */ while((entry = readdir(dptr)) != NULL && retval == RETVAL_OK) { /* ignore hidden files */ if(entry->d_name[0] != '.' && strstr(entry->d_name, tmp) != NULL) { i = fprintf(fptr, "%s\n", full_path(FP_GET_NOPOSTFIX, FP_MSGDIR, entry->d_name)); if(i <= 0) { retval = RETVAL_ERROR; MyPerror(master->batchfile); } } } fclose(fptr); closedir(dptr); } return retval; } /*----------------------------------------------------------*/ int do_rnewsbatch(PMaster master) { /* build rnews formated file of articles */ /* this is done by searching thru MSGDIR to find files */ /* which match our naming convention */ /* if max_file_size > 0, then create multiple files up to max file size */ int i, x, batchnr = 0, retval = RETVAL_OK; FILE *fptr = NULL, *fpin; const char *tptr, *tmp; char buf[MAXLINLEN]; DIR *dptr; struct dirent *entry; struct stat sbuf, tbuf; long cursize = 0L; print_phrases(master->msgs, batch_phrases[4], NULL); if((dptr = opendir(full_path(FP_GET, FP_MSGDIR, ""))) == NULL) { MyPerror(full_path(FP_GET, FP_MSGDIR, "")); retval = RETVAL_ERROR; } else { tmp = full_path(FP_GET_POSTFIX, 0, ""); /* this will be string we search for */ /* look for entries which have our postfix */ while(retval == RETVAL_OK && (entry = readdir(dptr))) { /* ignore hidden files */ if(entry->d_name[0] != '.' && strstr(entry->d_name, tmp) != NULL) { tptr = full_path(FP_GET_NOPOSTFIX, FP_MSGDIR, entry->d_name); if(stat(tptr, &sbuf) != 0 || (fpin = fopen(tptr, "r")) == NULL) { MyPerror(tptr); retval = RETVAL_ERROR; } else { if( cursize == 0 ) { if(fptr != NULL) { /* close old file */ fclose(fptr); batchnr++; } /* have to open file */ if(batchnr == 0) { strcpy(buf, master->batchfile); } else { sprintf(buf, "%s%d", master->batchfile, batchnr); } if(master->debug == TRUE) { do_debug("BATCH FILE: %s\n", buf); } if(stat(buf, &tbuf) == 0) { /* whoops file already exists */ MyPerror(buf); retval = RETVAL_ERROR; } else if((fptr = fopen(buf, "w")) == NULL) { MyPerror(buf); retval = RETVAL_ERROR; } } if(retval == RETVAL_OK) { /* first put #! rnews size */ fprintf(fptr, "#! rnews %ld\n", (long) sbuf.st_size); /* use fread/fwrite in case lines are longer than MAXLINLEN */ while((i = fread(buf, 1, MAXLINLEN, fpin)) > 0 && retval == RETVAL_OK) { x = fwrite(buf, 1, i, fptr); if(x != i) { /* error writing file */ retval = RETVAL_ERROR; MyPerror(buf); } } if(retval == RETVAL_OK ) { unlink(tptr); /* we are done with it, nuke it */ cursize += sbuf.st_size; /* keep track of current file size, we can ignore the #! rnews */ /* size, since it adds so little to the overall size */ if(master->rnews_size > 0L && cursize > master->rnews_size) { /* reached file size length */ cursize = 0L; /* this will force a close and open on next article */ } } } fclose(fpin); } } } if(fptr != NULL) { fclose(fptr); } closedir(dptr); } return retval; } /*----------------------------------------------------------------------------------*/ void do_lmovebatch(PMaster master) { /* fork lmove */ const char *args[LMOVE_MAX_ARGS]; int i, x; pid_t pid; print_phrases(master->msgs, batch_phrases[0], NULL); /* first build command */ args[0] = "lmove"; args[1] = "-c"; args[2] = master->batchfile; args[3] = "-d"; args[4] = full_path(FP_GET, FP_MSGDIR, ""); i = 5; if(master->errlog != NULL) { args[i++ ] = "-E"; args[i++ ] = master->errlog; } if(master->phrases != NULL) { args[i++] = "-l"; args[i++] = master->phrases; } if(master->debug == TRUE) { args[i++] = "-D"; } args[i] = NULL; if(master->debug == TRUE) { do_debug("Calling lmove with args:"); for(x = 0; x < i; x++) { do_debug(" %s", args[x]); } do_debug("\n"); } /* now fork and execl */ pid = fork(); if(pid == 0) { /* in child */ execvp(args[0], (char *const *) args); MyPerror(batch_phrases[2]); /* only get here on errors */ exit(-1); /* so we aren't running two sucks */ } else if(pid < 0) { /* whoops */ MyPerror(batch_phrases[1]); } else { /* in parent let the child finish */ wait(NULL); } } /*---------------------------------------------------------------------------*/ int do_localpost(PMaster master) { /* post articles to local server using IHAVE */ int sockfd, count = 0, retval = RETVAL_OK; FILE *fp_list; char msg[MAXLINLEN+1]; const char *fname; TimerFunc(TIMER_START, 0, NULL); print_phrases(master->msgs, batch_phrases[5], master->localhost, NULL); if(master->batchfile == NULL) { error_log(ERRLOG_REPORT, batch_phrases[6], NULL); retval = RETVAL_ERROR; } else { fname = full_path(FP_GET, FP_TMPDIR, master->batchfile); if((fp_list = fopen(fname, "r")) == NULL ) { MyPerror(fname); retval = RETVAL_ERROR; } else { if((sockfd = connect_local(master)) < 0) { retval = RETVAL_ERROR; } else { while(retval == RETVAL_OK && fgets(msg, MAXLINLEN, fp_list) != NULL) { retval = post_one_msg(master, sockfd, msg); if(retval == RETVAL_OK) { count++; } } disconnect_from_nntphost(sockfd, master->local_ssl, &master->local_ssl_struct); } fclose(fp_list); } if(retval == RETVAL_OK) { if(master->debug == TRUE) { do_debug("deleting %s\n", fname); } unlink(fname); } } print_phrases(master->msgs, batch_phrases[10], str_int(count), NULL); TimerFunc(TIMER_TIMEONLY, 0,master->msgs); return retval; } /*------------------------------------------------------------------------------------------------*/ int post_one_msg(PMaster master, int sockfd, char *msg) { int len, nr, longline, do_unlink = FALSE, retval = RETVAL_OK; char *msgid, *resp, linein[MAXLINLEN+4]; /* the extra in case of . in first pos */ FILE *fpi; /* msg contains the path and msgid */ msgid = strstr(msg, " <"); /* find the start of the msgid */ if(msgid == NULL) { error_log(ERRLOG_REPORT, batch_phrases[7], msg, NULL); } else { *msgid = '\0'; /* end the path name */ msgid++; /* so we point to the < */ len = strlen(msgid); /* strip a nl */ if(msgid[len-1] == '\n') { msgid[len-1] = '\0'; } if(master->debug == TRUE) { do_debug("File Name = \"%s\"\n", msg); } if((fpi = fopen(msg, "r")) == NULL) { MyPerror(msg); } else { sprintf(linein, "IHAVE %s\r\n", msgid); if(master->debug == TRUE) { do_debug("sending command %s", linein); } sputline(sockfd, linein, master->local_ssl, master->local_ssl_struct); if(sgetline(sockfd, &resp, master->local_ssl, master->local_ssl_struct) < 0) { retval = RETVAL_ERROR; } else { if(master->debug == TRUE) { do_debug("got answer: %s", resp); } number(resp, &nr); /* added for prob */ if(master->debug == TRUE) { do_debug("Answer=%d\n", nr); } if(nr == 435) { error_log(ERRLOG_REPORT, batch_phrases[11], msgid, resp, NULL); do_unlink = TRUE; } else if(nr != 335) { error_log(ERRLOG_REPORT, batch_phrases[8], msgid, resp, NULL); } else { /* send the article */ longline = FALSE; while(fgets(linein, MAXLINLEN, fpi) != NULL) { /* added for prob */ if(master->debug == TRUE) { do_debug("sending line-%s--\n", linein); } len = strlen(linein); if(longline == FALSE && linein[0] == '.') { /* double the . at beginning of line */ memmove(linein+1,linein,++len); linein[0] = '.'; } longline = ( linein[len - 1] == '\n' ) ? FALSE : TRUE ; if(longline == FALSE) { /* replace nl with cr nl */ strcpy(&linein[len-1], "\r\n"); } sputline(sockfd, linein, master->local_ssl, master->local_ssl_struct); } if(longline == TRUE) { /* end the last line */ sputline(sockfd, "\r\n", master->local_ssl, master->local_ssl_struct); } /* end the article */ sputline(sockfd, ".\r\n", master->local_ssl, master->local_ssl_struct); /* put in for prob */ if(master->debug == TRUE) { do_debug("Finished sending article\n"); } if(sgetline(sockfd, &resp, master->local_ssl, master->local_ssl_struct) < 0 ) { retval = RETVAL_ERROR; } else { if(master->debug == TRUE) { do_debug("Got response: %s", resp); } number(resp, &nr); if(nr == 437) { error_log(ERRLOG_REPORT, batch_phrases[12], msgid, resp, NULL); do_unlink = TRUE; } else if(nr == 235) { /* successfully posted, nuke it */ do_unlink = TRUE; } else { error_log(ERRLOG_REPORT, batch_phrases[9], msgid, resp, NULL); } } } } fclose(fpi); if(do_unlink == TRUE) { unlink(msg); } } } return retval; } /*--------------------------------------------------------------------------*/ void do_post_filter(PMaster master) { /* call lpost filter prgm with batchfile as argument */ const char *msgdir; pid_t pid; if(master->post_filter != NULL) { msgdir = full_path(FP_GET, FP_MSGDIR, ""); if(master->debug == TRUE) { do_debug("Running %s with %s as args\n", master->post_filter, msgdir); } pid = fork(); if(pid == 0) { /* in child */ if(execlp(master->post_filter, master->post_filter, msgdir, NULL) == -1) { /* big error */ MyPerror(master->post_filter); exit(-1); /* so we aren't running two sucks */ } } else if(pid == -1) { /* whoops */ MyPerror(master->post_filter); } else { /* parent, wait on child */ wait(NULL); } } } suck-4.3.4/batch.h000066400000000000000000000003301333033562000137130ustar00rootroot00000000000000#ifndef _SUCK_BATCH_H #define _SUCK_BATCH_H 1 int do_innbatch(PMaster); int do_rnewsbatch(PMaster); int do_localpost(PMaster); void do_lmovebatch(PMaster); void do_post_filter(PMaster); #endif /* _SUCK_BATCH_H */ suck-4.3.4/both.c000066400000000000000000000563741333033562000136040ustar00rootroot00000000000000#include #include #include #ifdef HAVE_UNISTD_H #include #include #endif #include #include #include #include #include #ifdef HAVE_ARPA_INET_H #include #endif #ifdef HAVE_NET_SOCKET_H #include #endif #include #include #ifdef HAVE_LIMITS_H #include #endif #ifdef DMALLOC #include #endif #include "suck_config.h" #include "both.h" #include "phrases.h" #ifdef TIMEOUT /*------------------------------------*/ int TimeOut = TIMEOUT; /* yes, this is the lazy way out, but */ /* there are too many routines that */ /* call sgetline() to modify em all */ /*------------------------------------*/ # if TIME_WITH_SYS_TIME # include # include # else # if HAVE_SYS_TIME_H # include # else # include # endif # endif #endif /* TIMEOUT */ #ifdef MYSIGNAL #include #endif #ifdef HAVE_SYS_SELECT_H #include /* for aix */ #endif #ifdef HAVE_LIBSSL #include #endif /* internal function proto */ void vprint_phrases(FILE *, const char *, va_list); void do_debug_vl(const char *, va_list); void convert_nl(char *); char *findnl(char *, char *); /*-----------------------------------------------------*/ /* get next number in string */ char *number(char *sp, int *intPtr) { int start, end; char c; char *retval; if(sp==NULL) { *intPtr=0; retval = sp; } else { /* skip any leading spaces */ start = 0; while(sp[start] == ' ') { start++; } end = start; while(isdigit(sp[end])) { end++; } /* now we have the numbers width*/ c=sp[end]; /* save off the character */ sp[end]='\0'; /* truncate nr so sscanf works right */ sscanf(&sp[start],"%d",intPtr); sp[end]=c; /* restore it back */ /* if at EOS return the NULL, else skip space */ retval = (sp[end] == '\0') ? sp+end : sp+(++end); } return retval; } /*----------------------------------------------------*/ /* identical to above, except it gets a long vice int */ char *get_long(char *sp, long *intPtr) { int start, end; char c; char *retval; if(sp==NULL) { *intPtr=0; retval = sp; } else { /* skip any leading spaces */ start = 0; while(sp[start] == ' ') { start++; } end = start; while(isdigit(sp[end])) { end++; } /* now we have the numbers width*/ c=sp[end]; /* save off the character */ sp[end]='\0'; /* truncate nr so sscanf works right */ sscanf(&sp[start],"%ld",intPtr); sp[end]=c; /* restore it back */ /* if at EOS return the NULL, else skip space */ retval = (sp[end] == '\0') ? sp+end : sp+(++end); } return retval; } /*---------------------------------------------*/ struct addrinfo *get_addrinfo(const char *host, const char *sport) { struct addrinfo hints = { .ai_socktype=SOCK_STREAM, .ai_flags = AI_CANONNAME }; struct addrinfo * res = NULL; if(host==NULL) { error_log(ERRLOG_REPORT,both_phrases[0], NULL); } else { int st = getaddrinfo(host, sport, &hints, &res); if (st < 0) { error_log(ERRLOG_REPORT, "%v1%: %v2%: %v3%\n", host, both_phrases[2], gai_strerror(st), NULL); } } return res; } /*--------------------------------------------*/ int connect_to_nntphost(const char *host, char * name, size_t namelen, FILE *msgs, unsigned short int portnr, int do_ssl, void **ssl) { char *realhost; char sport[10]; int sockfd = -1; struct addrinfo * ai; char buffer[60]; // if not given by caller. NI_MAXHOST would be better, but that's ok as well. if (host == NULL) { error_log(ERRLOG_REPORT, both_phrases[0], NULL); return sockfd; } #ifdef HAVE_LIBSSL SSL *ssl_struct = NULL; SSL_CTX *test1 = NULL; if(do_ssl == TRUE) { (void) SSL_library_init(); test1 = SSL_CTX_new(SSLv23_client_method()); if(test1 == NULL) { /* whoops */ error_log(ERRLOG_REPORT, both_phrases[18], NULL); return sockfd; } } #endif if (!name) { name = buffer; namelen = sizeof buffer; } /* handle host:port type syntax */ realhost = strdup(host); if(realhost == NULL) { MyPerror("out of memory copying host name"); return sockfd; } char * ptr = strchr(realhost, ':'); if(ptr != NULL) { *ptr = '\0'; /* null terminate host name */ portnr = atoi(++ptr); /* get port number */ } sprintf(sport, "%hu", portnr); /* cause print_phrases wants all strings */ print_phrases(msgs, both_phrases[1], sport, NULL); /* Find the internet addresses of the NNTP server */ ai = get_addrinfo(realhost, sport); if(ai == NULL) { free(realhost); } else { free(realhost); struct addrinfo * aii; print_phrases(msgs, both_phrases[3], ai->ai_canonname, NULL); for (aii=ai; aii; aii=aii->ai_next) { if (getnameinfo(aii->ai_addr, aii->ai_addrlen, name, namelen, NULL, 0, NI_NUMERICHOST) < 0) { name[0] = '\0'; } /* Create a socket */ if((sockfd = socket(aii->ai_family, aii->ai_socktype, aii->ai_protocol)) == -1) { continue; // MyPerror(both_phrases[6]); } else { /* Establish a connection */ if(connect(sockfd, aii->ai_addr, aii->ai_addrlen ) == -1) { //MyPerror(both_phrases[7]); close(sockfd); sockfd = -1; continue; } else { int st = getnameinfo(aii->ai_addr, aii->ai_addrlen, name, namelen, NULL, 0, NI_NUMERICHOST); print_phrases(msgs, both_phrases[8],st == 0 ? name : host, NULL); if (st != 0) name[0] = '\0'; break; } } } freeaddrinfo(ai); if (sockfd < 0) { MyPerror(both_phrases[6]); // or 7? } #ifdef HAVE_LIBSSL if(sockfd > -1 && do_ssl == TRUE) { if((ssl_struct = SSL_new(test1)) == NULL) { error_log(ERRLOG_REPORT, both_phrases[18], NULL); close(sockfd); sockfd = -1; } else if(SSL_set_fd(ssl_struct, sockfd) == FALSE) { error_log(ERRLOG_REPORT, both_phrases[18], NULL); close(sockfd); sockfd = -1; } else if(SSL_connect(ssl_struct) != 1) { error_log(ERRLOG_REPORT, both_phrases[18], NULL); close(sockfd); sockfd = -1; } else { *ssl = ssl_struct; } } #endif } return sockfd; } /*---------------------------------------------------------------*/ void disconnect_from_nntphost(int fd, int do_ssl, void **ssl) { #ifdef HAVE_LIBSSL if(do_ssl == TRUE) { fd = SSL_get_fd(*ssl); SSL_shutdown(*ssl); SSL_free(*ssl); *ssl = NULL; } #endif close(fd); } /*----------------------------------------------------------------*/ int sputline(int fd, const char *outbuf, int do_ssl, void *ssl_buf) { #ifdef DEBUG1 do_debug("\nSENT: %s", outbuf); #endif #ifdef HAVE_LIBSSL if(do_ssl == TRUE) { if(fd == SSL_get_fd((SSL *)ssl_buf)) { return SSL_write((SSL *)ssl_buf, outbuf, strlen(outbuf)); } else { return -1; } } #endif return send(fd, outbuf, strlen(outbuf), 0); } /*-------------------------------------------------------------*/ void do_debug(const char *fmt, ...) { FILE *fptr = NULL; va_list args; if((fptr = fopen(N_DEBUG, "a")) == NULL) { fptr = stderr; } va_start(args, fmt); vfprintf(fptr, fmt, args); va_end(args); if(fptr != stderr) { fclose(fptr); } } /*------------------------------------------------------------*/ void do_debug_binary(int len, const char *str) { FILE *fptr = NULL; if((fptr = fopen(N_DEBUG, "a")) == NULL) { fptr = stderr; } fwrite(str, sizeof(str[0]), len, fptr); if(fptr != stderr) { fclose(fptr); } } /*-----------------------------------------------------------*/ void do_debug_vl(const char *fmt, va_list args) { FILE *fptr = NULL; if((fptr = fopen(N_DEBUG, "a")) == NULL) { fptr = stderr; } vprint_phrases(fptr, fmt, args); if(fptr != stderr) { fclose(fptr); } } /*------------------------------------------------------------*/ void MyPerror(const char *message) { /* can't just use perror, since it goes to stderr */ /* and I need to route it to my errlog */ /* so I have to recreate perror's format */ /* in case of NULL ptr */ if(message == NULL) { message=""; } #ifdef HAVE_STRERROR error_log(ERRLOG_REPORT, "%v1%: %v2%\n", message, strerror(errno), NULL); #else error_log(ERRLOG_REPORT, both_phrases[9], message, errno, NULL); #endif } /*-----------------------------------------------------------*/ char *findnl(char *startbuf, char *endbuf) { /* find a \r\n combo in the buffer */ for(; startbuf < endbuf ; startbuf++) { if(*startbuf == '\r') { if(*(startbuf+1) == '\n' && (startbuf < endbuf-1)) { return startbuf; } } } return NULL; } /*-----------------------------------------------------------*/ int sgetline(int fd, char **inbuf, int do_ssl, void *ssl_buf) { static char buf[MAXLINLEN+MAXLINLEN+6]; static char *start = buf; static char *eob = buf; /* end of buffer */ int ret, i, len; char *ptr; #ifdef TIMEOUT fd_set myset; struct timeval mytimes; #endif ret = 0; ptr = NULL; if(fd < 0) { ret = -1; } else if(eob == start || (ptr = findnl(start, eob)) == NULL) { /* TEST for not a full line in buffer */ /* the eob == start test is needed in case the buffer is */ /* empty, since we don't know what is in it. */ len = eob-start; /* length of partial line in buf */ if((eob - buf) > MAXLINLEN) { #ifdef DEBUG1 do_debug("SHIFTING BUFFER\n"); #endif /* not enuf room in buffer for a full recv */ memmove(buf, start, len); /* move to start of buf */ eob = buf + len; *eob = '\0'; start = buf; /* reset pointers */ } /* try to get a line in, up to maxlen */ do { #ifdef DEBUG1 do_debug("\nCURRENT BUF start = %d, end = %d, len = %d\n", start - buf, eob - buf, len); #endif #ifdef TIMEOUT /* handle timeout value */ FD_ZERO(&myset); FD_SET(fd, &myset); mytimes.tv_sec = TimeOut; mytimes.tv_usec = 0; #ifdef MYSIGNAL signal_block(MYSIGNAL_BLOCK); /* block so we can't get interrupted by our signal defined in config.h */ #endif #ifdef HAVE_LIBSSL if(do_ssl == TRUE && fd == SSL_get_fd((SSL *)ssl_buf) && SSL_pending((SSL *)ssl_buf)) { i = 1; } else { #endif /* the fd+1 so we only scan our needed fd not all 1024 in set */ i = select(fd+1, &myset, (fd_set *) NULL, (fd_set *) NULL, &mytimes); #ifdef HAVE_LIBSSL } #endif if(i>0) { #ifdef HAVE_LIBSSL #ifdef DEBUG1 do_debug("SELECT got: %d\n", i); #endif if(do_ssl == TRUE) { if(fd == SSL_get_fd((SSL *)ssl_buf)) { i = SSL_read((SSL *)ssl_buf, eob, MAXLINLEN-len); } else { i = -1; } } else #endif i = recv(fd, eob, MAXLINLEN-len, 0); /* get line */ } else if(i == 0) { error_log(ERRLOG_REPORT, both_phrases[10], NULL); } /* other errors will be handled down below */ #else #ifdef HAVE_LIBSSL if(do_ssl == TRUE) { if(fd == SSL_get_fd((SSL *)ssl_buf)) { i = SSL_read((SSL *)ssl_buf, eob, MAXLINLEN-len); } else { i = -1; } } else #endif i = recv(fd, eob, MAXLINLEN-len, 0); /* get line */ #endif #ifdef MYSIGNAL signal_block(MYSIGNAL_UNBLOCK); /* we are done, now unblock it */ #endif #ifdef DEBUG1 do_debug("\nRECV returned %d", i); if ( i > 0) { do_debug("\nGOT: "); do_debug_binary(i,eob); do_debug(":END GOT"); } #endif if(i < 1) { if(i == 0) { /* No data read in either from recv or select timed out*/ error_log(ERRLOG_REPORT, both_phrases[11], NULL); } else { MyPerror(both_phrases[12]); } ret = -1; } else { eob += i; /* increment buffer end */ *eob = '\0'; /* NULL terminate it */ len += i; ptr = findnl(start, eob); } } while(ptr == NULL && len < MAXLINLEN && ret == 0); } if(ptr != NULL) { /* we have a full line left in buffer */ *ptr++ = '\n'; /* change \r\n to just \n */ *ptr++ = '\0'; /* null terminate */ *inbuf = start; ret = (ptr-1) - start; /* length of string */ start = ptr; /* skip \r\n */ } else if(ret == 0) { /* partial line in buffer */ /* null terminate */ *eob = '\0'; *inbuf = start; ret = (eob-1) - start; /* length of string */ start = ++eob; /* point both past end */ } #ifdef DEBUG1 if(ret > 0) { int flag = FALSE; char saveit = '\0'; /* change nl to something we can see */ if((*inbuf)[ret-1] == '\n') { flag = TRUE; (*inbuf)[ret-1] = '\\'; (*inbuf)[ret] = 'n'; saveit = (*inbuf)[ret+1]; (*inbuf)[ret+1] = '\0'; } do_debug("\nRETURNING len %d: %s\n", ret, *inbuf); /* put things back the way they were */ if(flag == TRUE) { (*inbuf)[ret-1] = '\n'; (*inbuf)[ret] = '\0'; (*inbuf)[ret+1] = saveit; } } else { do_debug("\nRETURNING error, ret = %d", ret); } #endif if(eob == start) { /* nothing left in buffer, reset pointers to start of buffer */ /* to give us a full buffer to receive next data into */ /* hopefully doing this will mean less buffer shifting */ /* meaning faster receives */ eob = start = buf; #ifdef DEBUG1 do_debug("EMPTY BUFFER, resetting to start\n"); /* take this out when debugged */ memset(buf, '\0', sizeof(buf)); #endif } return ret; } /*----------------------------------------------------*/ #ifdef MYSIGNAL void signal_block(int action) { #ifdef HAVE_SIGACTION static sigset_t blockers; static int do_block = FALSE; switch(action) { case MYSIGNAL_SETUP: /* This must be called first */ sigemptyset(&blockers); if(sigaddset(&blockers, MYSIGNAL) == -1 || sigaddset(&blockers,PAUSESIGNAL) == -1) { MyPerror(both_phrases[13]); } else { do_block = TRUE; } break; case MYSIGNAL_ADDPIPE: /* add SIGPIPE for killprg.c */ if(sigaddset(&blockers, SIGPIPE) == -1) { MyPerror(both_phrases[14]); } break; case MYSIGNAL_BLOCK: if(do_block == TRUE) { if(sigprocmask(SIG_BLOCK, &blockers, NULL) == -1) { MyPerror(both_phrases[13]); } } break; case MYSIGNAL_UNBLOCK: if(do_block == TRUE) { if(sigprocmask(SIG_UNBLOCK, &blockers, NULL) == -1) { MyPerror("Unable to unblock signal"); } } break; } #endif /* HAVE_SIGACTION */ } #endif /* MYSIGNAL */ /*-------------------------------------------------------------------*/ void error_log(int mode, const char *fmt, ...) { /* if we have been passed a file, report all errors to that file */ /* else report all errors to stderr */ /* handle printf type formats, hence the varargs stuff */ FILE *fptr = NULL; va_list args; static char errfile[PATH_MAX] = { '\0' }; static int debug = FALSE; va_start(args, fmt); /* set up args */ switch(mode) { case ERRLOG_SET_DEBUG: debug = TRUE; break; case ERRLOG_SET_FILE: strcpy(errfile,fmt); break; case ERRLOG_SET_STDERR: errfile[0] = '\0'; break; case ERRLOG_REPORT: if(errfile[0] == '\0' || (fptr = fopen(errfile, "a")) == NULL) { fptr = stderr; } vprint_phrases(fptr, fmt, args); if(debug == TRUE) { va_list args; va_start(args, fmt); do_debug_vl(fmt, args); va_end(args); } if(fptr != stderr) { fclose(fptr); } break; } va_end(args); /* so we can return normally */ return; } /*--------------------------------------------------------------------------*/ char **build_args(const char *fname, int *nrargs) { /* read a file and parse the args into an argv array */ /* make two passes thru the file, 1st count them so can allocate array */ /* then allocate each one individually */ char **args = NULL; char linein[MAXLINLEN+1]; int x, len, done, counter, argc = 0; FILE *fpi; if((fpi = fopen(fname, "r")) == NULL) { MyPerror(fname); } else { /* first pass, just count em */ counter = 0; while(fgets(linein, MAXLINLEN, fpi) != NULL) { x = 0; done = FALSE; while(linein[x] != '\0' && done == FALSE) { while(isspace(linein[x])) { x++; /* skip white space */ } if(linein[x] == FILE_ARG_COMMENT || linein[x] == '\0') { done = TRUE; /* skip rest of line */ } else { counter++; /* another arg */ while(!isspace(linein[x]) && linein[x] != '\0') { x++; /* skip rest of arg */ } } } } #ifdef DEBUG1 do_debug("Counted %d args in %s\n", counter, fname); #endif if((args = calloc( counter, sizeof(char *))) == NULL) { error_log(ERRLOG_REPORT, both_phrases[15], fname, NULL); } else { /* pass two read em and alloc em*/ fseek(fpi, 0L, SEEK_SET); /* rewind the file for pass two */ counter = 0; /* start at 0 again */ while(fgets(linein, MAXLINLEN, fpi) != NULL) { x = 0; done = FALSE; while(linein[x] != '\0' && done == FALSE) { while(isspace(linein[x])) { x++; /* skip white space */ } if(linein[x] == FILE_ARG_COMMENT || linein[x] == '\0') { done = TRUE; /* skip rest of line */ } else { /* have another arg */ len = 1; while(!isspace(linein[x+len]) && linein[x+len] != '\0') { len++; /* find length of arg */ } if((args[counter] = calloc( len+1, sizeof(char))) == NULL) { error_log(ERRLOG_REPORT, both_phrases[16], NULL); } else { strncpy(args[counter], &linein[x], len); args[counter][len] = '\0'; /* ensure null termination */ #ifdef DEBUG1 do_debug("Read arg #%d: '%s'\n", counter, args[counter]); #endif counter++; } x += len; /* go onto next one */ } } } argc = counter; /* total nr of args read in and alloced */ } fclose(fpi); } *nrargs = argc; return args; } /*-----------------------------------------------------------------------------*/ /* free the memory allocated in build_args() */ void free_args(int argc, char *argv[]) { int i; for(i=0;i 0 ) { block[len] = '\0'; /* null terminate string */ fputs(block, fpout); } } } } /*----------------------------------------------------------*/ char *str_int(int nrin) { static char strings[PHRASES_MAX_NR_VARS][12]; /* lets pray ints don't get bigger than this */ static int which = 0; /* use a set of rotating buffers so can make multiple str_int calls */ if(++which == PHRASES_MAX_NR_VARS) { which = 0; } sprintf(strings[which], "%d", nrin); return strings[which]; } /*----------------------------------------------------------*/ char *str_long(long nrin) { static char strings[PHRASES_MAX_NR_VARS][20]; /* lets pray longs don't get bigger than this */ static int which = 0; /* use a set of rotating buffers so can make multiple str_int calls */ if(++which == PHRASES_MAX_NR_VARS) { which = 0; } sprintf(strings[which], "%ld", nrin); return strings[which]; } /*-----------------------------------------------------------*/ /* print NULL or the string */ const char *null_str(const char *ptr) { const char *nullp = "NULL"; return (ptr == NULL) ? nullp : ptr; } /*-----------------------------------------------------------*/ /* print TRUE or FALSE if our nr is 0 or non-zero */ const char *true_str(int nr) { const char *true_s = "TRUE"; const char *false_s = "FALSE"; return (nr == TRUE) ? true_s : false_s; } suck-4.3.4/both.h000066400000000000000000000030201333033562000135650ustar00rootroot00000000000000#ifndef _SUCK_BOTH_H #define _SUCK_BOTH_H 1 #include /* for FILE */ #include #include /* for struct hostent */ #include "suck_config.h" /* for debug etc stuff */ /* declarations */ int sgetline(int fd, char **sbuf, int, void *); int sputline(int fd, const char *outbuf, int, void *); int connect_to_nntphost(const char *host, char *, size_t, FILE *, unsigned short int, int, void **); void disconnect_from_nntphost(int, int, void **); char *number(char *sp, int *intPtr); char *get_long(char *, long *); struct hostent *get_hostent(const char *host); void signal_block(int); void error_log(int mode, const char *fmt, ...); void MyPerror(const char *); void free_args(int, char *[]); char **build_args(const char *, int *); char **read_array(FILE *, int, int); void free_array(int, char **); char *do_a_phrase(char []); void print_phrases(FILE *, const char *, ...); char *str_int(int); char *str_long(long); void do_debug(const char *, ...); void do_debug_binary(int, const char *); const char *null_str(const char *); const char *true_str(int); #define N_DEBUG "debug.suck" enum { MYSIGNAL_SETUP, MYSIGNAL_BLOCK, MYSIGNAL_UNBLOCK, MYSIGNAL_ADDPIPE }; enum { ERRLOG_SET_FILE, ERRLOG_SET_STDERR, ERRLOG_REPORT, ERRLOG_SET_DEBUG }; #define SOCKET_PROTOCOL 0 /* so testhost.c can get at it */ #ifndef FALSE #define FALSE 0 #define TRUE !FALSE #endif #ifdef TIMEOUT extern int TimeOut; /* used to pass TimeOut value to sgetline() */ #endif #ifdef HAVE_SOCKS_H #include #endif #endif /* _SUCK_BOTH_H */ suck-4.3.4/both_phrases.c000066400000000000000000000013261333033562000153140ustar00rootroot00000000000000const char *default_both_phrases[] = { "No hostname given!\n", /* 0 */ "Using Port %v1%\n", "Could not get host information", "Official host name: %v1%\n", "Alias %v1%\n", "Unsupported address type\n", /* 5 */ "Socket Failed", "Connect Failed", "Connected to %v1%\n", "%v1%: Errno %v2%\n", "Socket error: Timed out on Read\n", /* 10 */ "Socket error: No data to read\n", "Socket error", "Unable to block signal %v1%\n", "Unable to block SIGPIPE, if child dies, we will die\n", "Out of memory reading %v1%, ignoring\n", /* 15 */ "Out of memory reading argument file\n", "Address: %v1%\n", "Unable to initialize SSL\n", }; int nr_both_phrases=sizeof(default_both_phrases)/sizeof(default_both_phrases[0]); suck-4.3.4/chkhistory.c000066400000000000000000000065361333033562000150320ustar00rootroot00000000000000#include /* If we don't use history databases, then these routines will */ /* get compiled and used. If we do, the routines in chkhistory_db.c will */ /* be used. */ #include #include #include #ifdef DMALLOC #include #endif #include "suck_config.h" #include "suck.h" #include "both.h" #include "chkhistory.h" #include "suckutils.h" #include "phrases.h" #include "timer.h" #include "ssort.h" /* this is almost a duplicate of dedupe_list() */ /* put items in array, qsort it, then work thru history file, and use bsearch to match em */ void chkhistory(PMaster master) { FILE *fhist; PList *array, ptr, curr, prev, tptr; int nrin=0, nrfound=0; long valinhist = 0, nrinhist = 0; char *cptr, linein[MAXLINLEN+1]; if((fhist = fopen(master->history_file, "r")) == NULL) { MyPerror(master->history_file); } else { if(master->debug == TRUE) { do_debug("Chking %d master->nritems\nReading history file - %s\n", master->nritems, master->history_file); } print_phrases(master->msgs, chkh_phrases[1], NULL); fflush(master->msgs); /* so msg gets printed */ TimerFunc(TIMER_START, 0L, NULL); /* 1. throw em into the array */ if((array = calloc(master->nritems, sizeof(PList))) == NULL) { error_log(ERRLOG_REPORT, chkh_phrases[4], NULL); } else { ptr = master->head; nrin = 0; while(ptr != NULL) { array[nrin] = ptr; nrin++; ptr = ptr->next; } if(master->debug == TRUE) { do_debug("Added %d items to array for ssort, master->nritems = %d\n", nrin, master->nritems); } /* step 2, sort em */ ssort(array, nrin, 1); /* start with depth of 1 cause all start with < */ /* step 3, find and mark dupes */ /* history file can have two valid types of lines */ /* the ones we are interested in is the ones that start with < */ /* we ignore the ones that start with [ and report the rest */ while(fgets(linein, MAXLINLEN, fhist) != NULL) { nrinhist++; if(linein[0] == '<') { /* make sure we have valid msgid and null terminate it */ if((cptr = strchr(linein, '>')) == NULL) { error_log(ERRLOG_REPORT, chkh_phrases[2], linein, NULL); } else { valinhist++; *(cptr+1) = '\0'; if((tptr = my_bsearch(array, linein, nrin)) != NULL) { /* found in our array flag for deletion */ /* it returns a PList * */ tptr->delete = TRUE; nrfound++; } } } } if(ferror(fhist) != 0) { error_log(ERRLOG_REPORT, chkh_phrases[6], master->history_file, NULL); clearerr(fhist); } if(master->debug == TRUE) { do_debug("%d lines in history, %d valid lines, %d duplicates found\n", nrinhist, valinhist, nrfound); } /* step 4, delete em */ curr = master->head; prev = NULL; while(curr != NULL) { if( curr->delete == TRUE) { /* nuke it */ master->nritems--; if(prev == NULL) { /* remove master node */ master->head = curr->next; free_one_node(curr); curr = master->head; } else { prev->next = curr->next; free_one_node(curr); curr = prev->next; } } else { prev = curr; curr = curr->next; } } /* all done free up mem */ free(array); } fclose(fhist); TimerFunc(TIMER_TIMEONLY, 0l, master->msgs); print_phrases(master->msgs, chkh_phrases[3], str_int(nrfound), NULL); } } suck-4.3.4/chkhistory.h000066400000000000000000000001641333033562000150260ustar00rootroot00000000000000#ifndef _SUCK_CHKHISTORY_H #define _SUCK_CHKHISTORY_H 1 void chkhistory(PMaster); #endif /* _SUCK_CHKHISTORY_H */ suck-4.3.4/chkhistory_db.c000066400000000000000000000116401333033562000154670ustar00rootroot00000000000000#include /* If we use history databases, then these routines will */ /* get compiled and used. If we don't, the routines in chkhistory.c will */ /* be used. */ #include #include "suck_config.h" #include "suck.h" #include "both.h" #include "chkhistory.h" #include "suckutils.h" #include "phrases.h" #include "timer.h" /* These take care if user had multiple defines in makefile */ /* use DBM if we've got it */ #ifdef USE_DBM #undef USE_GDBM #undef USE_NDBM #undef USE_DBZ #undef USE_INN2 #undef USE_INN23 #endif /* else if have GDBM use it */ #ifdef USE_GDBM #undef USE_NDBM #undef USE_DBZ #undef USE_INN2 #undef USE_INN23 #endif /* else if have NDBM use it */ #ifdef USE_NDBM #undef USE_DBZ #undef USE_INN2 #undef USE_INN23 #endif /* use DBZ if we have it */ #ifdef USE_DBZ #undef USE_INN2 #undef USE_INN23 #endif /* use INN2 if we have it */ #ifdef USE_INN2 #undef USE_INN23 #endif #ifdef USE_DBM #include #define close_history() dbmclose(); #endif #ifdef USE_GDBM #include static GDBM_FILE dbf = NULL; #define close_history() gdbm_close(dbf) #endif #ifdef USE_NDBM #include #ifdef HAVE_FCNTL_H #include #endif static DBM *db = NULL; /* I know this isn't too pretty, but its the easiest way to do it */ #define close_history() dbm_close(db) #endif #ifdef USE_DBZ #include #define close_history() dbmclose() #endif #ifdef USE_INN2 #include #include #include #include #include #define close_history() dbzclose() #endif #ifdef USE_INN23 #include #include #include #define close_history() dbzclose() #define USE_INN2 /* we need the rest of the inn2 code, only the includes are different */ #endif #ifdef DMALLOC #include #endif /* function prototypes */ int open_history(const char *); int check_history(char *); /*---------------------------------------------------------------------------------------------------*/ void chkhistory(PMaster master) { PList curr, prev; int nrfound = 0; if(master->debug == TRUE) { do_debug("Opening history database: %s\n", master->history_file); } if(open_history(master->history_file) != TRUE) { error_log(ERRLOG_REPORT, chkh_phrases[0], master->history_file, NULL); } else { print_phrases(master->msgs, chkh_phrases[5], NULL); fflush(master->msgs); /* so msg gets printed */ TimerFunc(TIMER_START, 0L, NULL); /* okay cycle thru our list, checking each against the history DB */ curr = master->head; prev = NULL; while(curr != NULL ) { if(check_history(curr->msgnr) == TRUE) { if(master->debug == TRUE) { do_debug("Matched %s, nuking\n", curr->msgnr); } /* matched, nuke it */ nrfound++; master->nritems--; if(prev == NULL) { /* remove master node */ master->head = curr->next; free_one_node(curr); curr = master->head; /* next node to check */ } else { prev->next = curr->next; free_one_node(curr); curr = prev->next; /* next node to check */ } } else { if(master->debug == TRUE) { do_debug("Didn't match %s\n", curr->msgnr); } /* next node to check */ prev = curr; curr = curr->next; } } TimerFunc(TIMER_TIMEONLY, 0l, master->msgs); close_history(); print_phrases(master->msgs, chkh_phrases[3], str_int(nrfound), NULL); } return; } /*------------------------------------------------------------------------*/ int open_history(const char *history_file) { int retval = FALSE; #if defined (USE_DBM) || defined (USE_DBZ) if(dbminit(history_file) >= 0) { retval = TRUE; } #endif #if defined (USE_INN2) /* first, set our options */ dbzoptions opt; #ifdef DO_TAGGED_HASH opt.pag_incore=INCORE_MEM; #else #ifndef USE_INN23 opt.idx_incore=INCORE_MEM; /* per man page speeds things up, inn-2.3 doesn't need this */ #endif #endif opt.exists_incore=INCORE_MEM; dbzsetoptions(opt); retval = dbzinit(history_file); #endif #if defined (USE_NDBM) if((db = dbm_open(history_file, O_RDONLY, 0)) != NULL) { retval = TRUE; } #endif #if defined(USE_GDBM) if((dbf = gdbm_open(history_file, 1024, GDBM_READER,0,0)) != NULL) { retval = TRUE; } #endif return retval; } /*----------------------------------------------------------------*/ int check_history(char *msgid) { #if defined (USE_DBM) || defined (USE_DBZ) || defined (USE_NDBM) datum input, result; input.dptr = msgid; input.dsize = strlen(msgid) +1; #if defined (USE_DBM) result = fetch(input); #elif defined (USE_DBZ) result = dbzfetch(input); #elif defined (USE_NDBM) result = dbm_fetch(db, input); #endif return (result.dptr != NULL) ? TRUE : FALSE; #endif /* big if defined */ #if defined (USE_INN2) HASH msgid_hash; msgid_hash=HashMessageID(msgid); /* have to hash it first */ return dbzexists(msgid_hash); #endif #if defined(USE_GDBM) datum input; input.dptr = msgid; input.dsize = strlen(msgid)+1; return gdbm_exists(dbf, input); #endif } suck-4.3.4/configure.ac000066400000000000000000000071161333033562000147600ustar00rootroot00000000000000dnl Process this file with autoconf to produce a configure script. dnl Autoconfigure input file for suck AC_INIT(suck.c) AC_CONFIG_HEADER(config.h) AC_ARG_WITH(inn-lib, [ --with-inn-lib=path location of libinn.a]) AC_ARG_WITH(inn-include, [ --with-inn-include=path location of inn include files]) AC_ARG_WITH(perl-exe, [ --with-perl-exe=full path to perl executable eg: /usr/bin]) # we want these before the checks, so the checks can modify their values test -z "$CFLAGS" && CFLAGS=-O2 AC_SUBST(CFLAGS) test -z "$LDFLAGS" && LDFLAGS=-s AC_SUBST(LDFLAGS) AC_PROG_CC AC_PROG_INSTALL AC_PROG_MAKE_SET AC_SUBST(GCC) dnl Checks for libraries. AC_CHECK_LIB(nsl, gethostbyname) AC_CHECK_LIB(socket, socket) AC_CHECK_LIB(socks, Rconnect) AC_CHECK_LIB(socks5, SOCKSconnect) AC_CHECK_LIB(cV, fprintf) dnl Do I need -lcrypto????? for SSL AC_CHECK_LIB(ssl, SSL_get_error, [SSL_LIB="-lssl"; SSL_DEFS="-DHAVE_LIBSSL"]) dnl Checks for header files. AC_HEADER_DIRENT AC_HEADER_STDC AC_HEADER_SYS_WAIT AC_CHECK_HEADERS(fcntl.h sys/time.h unistd.h limits.h regex.h sys/select.h net/socket.h arpa/inet.h socks.h) dnl Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_TYPE_PID_T AC_TYPE_SIZE_T AC_HEADER_TIME dnl Checks for library functions. AC_TYPE_SIGNAL AC_CHECK_FUNCS(gettimeofday select strerror memmove setvbuf sigaction) if test "$with_inn_lib" ; then LDFLAGS+="$DB_LIB -L${with_inn_lib}" fi if test "$with_inn_include" ; then testpath=$with_inn_include else testpath="/usr/include/inn /usr/local/include /usr/local/include/inn" fi HISTORY="chkhistory_db.o" AC_CHECK_LIB(inn, main, [ DB_LIB="-linn" DB_TYPE="-DUSE_INN23" savedLIBS=$LIBS LIBS="$LIBS -linn" AC_CHECK_FUNC(QIOopen, , [ LIBS="$savedLIBS -lstorage" AC_CHECK_FUNC(QIOopen, [DB_LIB="$DB_LIB -lstorage"]) ]) LIBS=$savedLIBS AC_MSG_CHECKING([for libinn.h]) for path in $testpath ; do if [ test -f $path/libinn.h ] || [ test -f $path/inn/libinn.h ] ; then AC_MSG_RESULT($path) found="yes" CPPFLAGS="$CPPFLAGS -I$path" break fi done test "$found" != "yes" && AC_MSG_RESULT(not found) ], [ AC_CHECK_LIB(dbz, main, [DB_LIB="-ldbz"; DB_TYPE="-DUSE_DBZ"], [ AC_CHECK_LIB(ndbm, main, [DB_LIB="-lndbm"; DB_TYPE="-DUSE_NDBM"], [ AC_CHECK_LIB(gdbm, main, [DB_LIB="-lgdbm"; DB_TYPE="-DUSE_GDBM"], [ AC_CHECK_LIB(dbm, main, [DB_LIB="-ldbm"; DB_TYPE="-DUSE_DBM"], [ HISTORY="chkhistory.o"])])])]) ]) if test "$with_perl_exe" ; then AC_CHECK_PROG(PERL, perl, true, false, [$with_perl_exe]) whichperl=${with_perl_exe}/perl else AC_CHECK_PROG(PERL, perl, true, false) whichperl=perl fi if test "$PERL" = "true"; then found="no" AC_MSG_CHECKING([for libperl.a]) for path in `$whichperl -e 'foreach $i (@INC) { printf("%s ", $i) }'`; do if test -f $path/libperl.a; then AC_MSG_RESULT($path) found="yes" break fi path="${path}/CORE" if test -f $path/libperl.a; then AC_MSG_RESULT($path) found="yes" break fi done if test "$found" = "yes"; then PERL_DEFS="-DPERL_EMBED -Dbool=char -DHAS_BOOL" PERL_LIB="-lperl" PERL_LIB_LOC="-L$path" PERL_INC_LOC="-I$path" AC_CHECK_LIB(m, cos, [PERL_LIB="$PERL_LIB -lm"]) AC_CHECK_LIB(crypt, crypt, [PERL_LIB="$PERL_LIB -lcrypt"]) else AC_MSG_RESULT(not found) fi fi #AC_OUTPUT(Spanish.docs/Makefile Makefile, echo timestamp > stamp-h) AC_SUBST(DB_TYPE) AC_SUBST(DB_LIB) AC_SUBST(HISTORY) AC_SUBST(SSL_LIB) AC_SUBST(SSL_DEFS) AC_SUBST(PERL_DEFS) AC_SUBST(PERL_LIB) AC_SUBST(PERL_LIB_LOC) AC_SUBST(PERL_INC_LOC) AC_OUTPUT(Makefile, echo timestamp > stamp-h) suck-4.3.4/db.c000066400000000000000000000151221333033562000132170ustar00rootroot00000000000000#include #include #include #include #include #ifdef HAVE_FCNTL_H #include #endif #ifndef O_SYNC #define O_SYNC O_FSYNC #endif #ifdef HAVE_UNISTD_H #include #include #endif #ifdef DMALLOC #include #endif #include "suck_config.h" #include "suck.h" #include "suckutils.h" #include "both.h" #include "phrases.h" #include "db.h" /* ------------------------------------------------------------------------------ */ /* this file handles all the db routines, creating, deleting, adding records to, */ /* flagging as downloaded, etc. It replaces the old suck.restart and suck.sorted */ /* ------------------------------------------------------------------------------ */ int db_delete(PMaster master) { int retval = RETVAL_OK; const char *ptr; db_close(master); /* just in case */ ptr = full_path(FP_GET, FP_TMPDIR, N_DBFILE); if(master->debug == TRUE) { do_debug("Unlinking %s\n", ptr); } if(unlink(ptr) != 0 && errno != ENOENT) { MyPerror(ptr); retval = RETVAL_ERROR; } return retval; } /*------------------------------------------------------------------------------*/ int db_write(PMaster master) { /* write out the entire database */ int retval = RETVAL_OK; const char *fname; int fd; long itemnr, grpnr; PList itemon; PGroups grps; fname = full_path(FP_GET, FP_TMPDIR, N_DBFILE); if(master->debug == TRUE) { do_debug("Writing entire db - %s\n", fname); } if((fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC #ifdef EMX /* OS-2 */ | O_BINARY #endif , S_IRUSR | S_IWUSR)) == -1) { retval = RETVAL_ERROR; MyPerror(fname); } else { /* start at the head of the list, add our dbnr */ itemon = master->head; /* first write out number of items, so can read em in later*/ itemnr = master->nritems; write(fd, &itemnr, sizeof(itemnr)); /* now write the list */ itemon = master->head; itemnr = 0L; while ( itemon != NULL && retval == RETVAL_OK) { itemon->dbnr = itemnr; if(write(fd, itemon, sizeof(List)) != sizeof(List)) { error_log(ERRLOG_REPORT, suck_phrases[23], NULL); retval = RETVAL_ERROR; } itemon = itemon->next; itemnr++; } /* now write out the groups */ /* first write out the number of items, so can read em in later */ grps = master->groups; grpnr = 0; while(grps != NULL) { grps = grps->next; grpnr++; } write(fd, &grpnr, sizeof(grpnr)); grps = master->groups; grpnr = 0L; while (grps != NULL) { if(write(fd, grps, sizeof(Groups)) != sizeof(Groups)) { error_log(ERRLOG_REPORT, suck_phrases[23], NULL); retval = RETVAL_ERROR; } grps = grps->next; grpnr++; } close(fd); } return retval; } /*--------------------------------------------------------------------------------*/ int db_read(PMaster master) { /* read in the entire database, and add it to the end of the current list */ int retval = RETVAL_OK; const char *fname; int fd; PList itemon, ptr; PGroups grpon, gptr; long itemnr; fname = full_path(FP_GET, FP_TMPDIR, N_DBFILE); if(master->debug == TRUE) { do_debug("Reading entire db - %s\n", fname); } if((fd = open(fname, O_RDONLY #ifdef EMX /* OS-2 */ | O_BINARY #endif )) != -1) { /* read em in */ print_phrases(master->msgs, suck_phrases[2], NULL); read(fd, &itemnr, sizeof(itemon)); /* get the number of items */ master->nritems = itemnr; itemon = NULL; /* have to malloc em first */ do { if((ptr = malloc(sizeof(List))) == NULL) { retval = RETVAL_ERROR; error_log(ERRLOG_REPORT, suck_phrases[22], NULL); } else { if(read(fd, ptr, sizeof(List)) == sizeof(List)) { itemnr--; /* add to list */ if( master->head == NULL) { master->head = ptr; } else { itemon->next = ptr; } itemon = ptr; /* so we can track where we're at */ if(master->debug == TRUE) { do_debug("restart-read %s-%d-%ld-%d-%d-%d\n", ptr->msgnr, ptr->groupnr, ptr->nr, ptr->mandatory, ptr->downloaded, ptr->delete, ptr->sentcmd); } } else { retval = RETVAL_ERROR; /* didn't get in what we expected */ MyPerror(fname); } } } while(retval == RETVAL_OK && itemnr > 0); /* now get the groups */ /* get the count first */ read(fd, &itemnr, sizeof(itemnr)); grpon = NULL; do { if((gptr = malloc(sizeof(Groups))) == NULL) { retval = RETVAL_ERROR; error_log(ERRLOG_REPORT, suck_phrases[22], NULL); } else { if(read(fd, gptr, sizeof(Groups)) == sizeof(Groups)) { itemnr--; if( master->groups == NULL) { master->groups = gptr; } else { grpon->next = gptr; } grpon = gptr; } else { retval = RETVAL_ERROR; MyPerror(fname); } } } while(retval == RETVAL_OK && itemnr > 0); close(fd); if(retval != RETVAL_OK) { /* free up what we brought in */ itemon = master->head; master->nritems = 0; while (itemon != NULL) { ptr = itemon->next; free(itemon); itemon = ptr; } grpon = master->groups; while(grpon != NULL) { gptr = grpon->next; free(grpon); grpon = gptr; } master->head = NULL; master->groups = NULL; } } return retval; } /*-----------------------------------------------------------------------------*/ int db_open(PMaster master) { int retval = RETVAL_OK; const char *fname; if(master->db != -1) { /* just to be on the safe side */ close(master->db); } fname = full_path(FP_GET, FP_TMPDIR, N_DBFILE); /* we have the sync on it, so that we force the write to disk */ if((master->db = open (fname, O_WRONLY | O_SYNC #ifdef EMX | O_BINARY #endif )) == -1) { MyPerror(fname); retval = RETVAL_ERROR; } return retval; } /*-----------------------------------------------------------------------------*/ void db_close(PMaster master) { if(master->db != -1) { close(master->db); } } /*------------------------------------------------------------------------------*/ int db_mark_dled(PMaster master, PList itemon) { int retval = RETVAL_OK; off_t offset; /* first figure out where we need to be writing to */ offset = sizeof(long) + (itemon->dbnr * sizeof(List)); /* the extra long to skip the number we write out at beginning of file */ if(lseek(master->db, offset, SEEK_SET) == (off_t)-1) { MyPerror(full_path(FP_GET, FP_TMPDIR, N_DBFILE)); retval = RETVAL_ERROR; } else { itemon->downloaded = TRUE; if(write(master->db, itemon, sizeof(List)) != sizeof(List)) { MyPerror(full_path(FP_GET, FP_TMPDIR, N_DBFILE)); error_log(ERRLOG_REPORT, suck_phrases[23], NULL); retval = RETVAL_ERROR; } } return retval; } suck-4.3.4/db.h000066400000000000000000000003571333033562000132300ustar00rootroot00000000000000#ifndef _SUCK_DB_H #define _SUCK_DB_H /* function prototypes */ int db_delete(PMaster); int db_write(PMaster); int db_read(PMaster); int db_mark_dled(PMaster, PList); int db_open(PMaster); void db_close(PMaster); #endif /* _SUCK_DB_H */ suck-4.3.4/dedupe.c000066400000000000000000000042701333033562000141020ustar00rootroot00000000000000#include #include #include #include #ifdef DMALLOC #include #endif #include "suck_config.h" #include "suck.h" #include "both.h" #include "dedupe.h" #include "suckutils.h" #include "phrases.h" #include "timer.h" #include "ssort.h" /* this is almost a duplicate of chkhistory() routine. */ void dedupe_list(PMaster master) { /* throw the items into an array, qsort em, and then just compare items in a row to dedupe */ PList *array, ptr, curr, prev; int i=0, nrfound=0; print_phrases(master->msgs, dedupe_phrases[0], NULL); fflush(master->msgs); /* so msg gets printed */ TimerFunc(TIMER_START, 0L, NULL); /* 1. throw em into the array */ if((array = calloc(master->nritems, sizeof(PList))) == NULL) { error_log(ERRLOG_REPORT, dedupe_phrases[1], NULL); } else { ptr = master->head; i = 0; while(ptr != NULL) { array[i] = ptr; i++; ptr = ptr->next; } TimerFunc(TIMER_START, 0L, NULL) ; /* step 2, sort em */ ssort(array, master->nritems, 1); /* start with depth of 1 cause all start with < */ /* TimerFunc(TIMER_TIMEONLY, 0L, master->msgs); */ /* step 3, mark dupes */ for(i=0;i<(master->nritems-1);i++) { if(cmp_msgid(array[i]->msgnr,array[i+1]->msgnr) == TRUE) { /* if this is one we've already downloaded, or one we've already marked as dupe */ /* delete the other one */ if(array[i]->delete == TRUE || array[i]->downloaded == TRUE) { array[i+1]->delete = TRUE; } else { array[i]->delete = TRUE; } nrfound++; } } /* step 4, delete em */ curr = master->head; prev = NULL; while(curr != NULL) { if( curr->delete == TRUE) { /* nuke it */ master->nritems--; if(prev == NULL) { /* remove master node */ master->head = curr->next; free_one_node(curr); curr = master->head; } else { prev->next = curr->next; free_one_node(curr); curr = prev->next; } } else { prev = curr; curr = curr->next; } } /* all done free up mem */ free(array); } TimerFunc(TIMER_TIMEONLY, 0l, master->msgs); print_phrases(master->msgs, dedupe_phrases[2], str_int(master->nritems), str_int(nrfound), NULL); } suck-4.3.4/dedupe.h000066400000000000000000000001511333033562000141010ustar00rootroot00000000000000#ifndef _SUCK_DEDUPE_H #define _SUCK_DEDUPE_H 1 void dedupe_list(PMaster); #endif /* _SUCK_DEDUPE_H */ suck-4.3.4/install-sh000077500000000000000000000112451333033562000144740ustar00rootroot00000000000000#! /bin/sh # # install - install a program, script, or datafile # This comes from X11R5. # # Calling this script install-sh is preferred over install.sh, to prevent # `make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. # # set DOITPROG to echo to test this script # Don't use :- since 4.3BSD and earlier shells don't like it. doit="${DOITPROG-}" # put in absolute paths if you don't have them in your path; or use env. vars. mvprog="${MVPROG-mv}" cpprog="${CPPROG-cp}" chmodprog="${CHMODPROG-chmod}" chownprog="${CHOWNPROG-chown}" chgrpprog="${CHGRPPROG-chgrp}" stripprog="${STRIPPROG-strip}" rmprog="${RMPROG-rm}" mkdirprog="${MKDIRPROG-mkdir}" transformbasename="" transform_arg="" instcmd="$mvprog" chmodcmd="$chmodprog 0755" chowncmd="" chgrpcmd="" stripcmd="" rmcmd="$rmprog -f" mvcmd="$mvprog" src="" dst="" dir_arg="" while [ x"$1" != x ]; do case $1 in -c) instcmd="$cpprog" shift continue;; -d) dir_arg=true shift continue;; -m) chmodcmd="$chmodprog $2" shift shift continue;; -o) chowncmd="$chownprog $2" shift shift continue;; -g) chgrpcmd="$chgrpprog $2" shift shift continue;; -s) stripcmd="$stripprog" shift continue;; -t=*) transformarg=`echo $1 | sed 's/-t=//'` shift continue;; -b=*) transformbasename=`echo $1 | sed 's/-b=//'` shift continue;; *) if [ x"$src" = x ] then src=$1 else # this colon is to work around a 386BSD /bin/sh bug : dst=$1 fi shift continue;; esac done if [ x"$src" = x ] then echo "install: no input file specified" exit 1 else true fi if [ x"$dir_arg" != x ]; then dst=$src src="" if [ -d $dst ]; then instcmd=: else instcmd=mkdir fi else # Waiting for this to be detected by the "$instcmd $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if [ -f $src -o -d $src ] then true else echo "install: $src does not exist" exit 1 fi if [ x"$dst" = x ] then echo "install: no destination specified" exit 1 else true fi # If destination is a directory, append the input filename; if your system # does not like double slashes in filenames, you may need to add some logic if [ -d $dst ] then dst="$dst"/`basename $src` else true fi fi ## this sed command emulates the dirname command dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` # Make sure that the destination directory exists. # this part is taken from Noah Friedman's mkinstalldirs script # Skip lots of stat calls in the usual case. if [ ! -d "$dstdir" ]; then defaultIFS=' ' IFS="${IFS-${defaultIFS}}" oIFS="${IFS}" # Some sh's can't handle IFS=/ for some reason. IFS='%' set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` IFS="${oIFS}" pathcomp='' while [ $# -ne 0 ] ; do pathcomp="${pathcomp}${1}" shift if [ ! -d "${pathcomp}" ] ; then $mkdirprog "${pathcomp}" else true fi pathcomp="${pathcomp}/" done fi if [ x"$dir_arg" != x ] then $doit $instcmd $dst && if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi else # If we're going to rename the final executable, determine the name now. if [ x"$transformarg" = x ] then dstfile=`basename $dst` else dstfile=`basename $dst $transformbasename | sed $transformarg`$transformbasename fi # don't allow the sed command to completely eliminate the filename if [ x"$dstfile" = x ] then dstfile=`basename $dst` else true fi # Make a temp file name in the proper directory. dsttmp=$dstdir/#inst.$$# # Move or copy the file name to the temp name $doit $instcmd $src $dsttmp && trap "rm -f ${dsttmp}" 0 && # and set any options; do chmod last to preserve setuid bits # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $instcmd $src $dsttmp" command. if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && # Now rename the file to the real destination. $doit $rmcmd -f $dstdir/$dstfile && $doit $mvcmd $dsttmp $dstdir/$dstfile fi && exit 0 suck-4.3.4/java/000077500000000000000000000000001333033562000134065ustar00rootroot00000000000000suck-4.3.4/java/README000066400000000000000000000037071333033562000142750ustar00rootroot00000000000000Recently, I decided to learn a bit about JAVA programming. So, to do this, I threw together a GUI for suck, primarily as a learning experience. This GUI basically takes the status and error messages from a suck run, and puts them in scrollable areas. The only tough part to this was how to handle the BPS and count parts of the display, since they would very quickly overflow any display buffer I had. Due to the way suck prints them out, there wasn't an easy way to separate these from the rest of the status messages. So, I added the -G option. The -G option changes the BPS and Count output to a format easier to pick out in a program. It outputs "---BPS+++NR". This is used by my Java program so that it can display the BPS and count in a separate window. If you want to use the GUI - I have provided both the Java source code, and the .class files, in case you don't have a java compiler. You'll still to get the Java runtime kit. For linux, go to . For other OSes, try , the official Sun Site. Because Suck.java calls a child program, it can't run as an applet. This is a built-in security measure in java. The following is the script I use to run it. ------------------------------------------------------------------ #!/bin/sh CLASSPATH="/home/boby/doNews/" export CLASSPATH; /usr/local/jdk1.1.6/bin/java Suck ${CLASSPATH}/get.news.wn.sh ------------------------------------------------------------------ You'll need to change 3 things: 1. CLASSPATH= this needs to point where the Suck*.class files live. 2. /usr/local/jdk1.1.6/bin/java this needs to point to where the java runtime executable lives. 3. ${CLASSPATH}/get.news.wn this needs to point to the shell/program you normally would use to run suck. In my case, I keep the class files in the same directory as the rest of my suck stuff. If any one wants to expand this, feel free to, and I'll include it in future version. suck-4.3.4/java/Suck.class000066400000000000000000000055351333033562000153520ustar00rootroot00000000000000Êþº¾-¾g€ƒ„†Š‹Œ—½‘“”žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±² J J J J "J K L M &N O O O P Q R S T U V $W %X #Y #Z $[ %\ !] !^ _ ` a b c d e &f j l u v x y z { •m •n •o •q –t ˜} ™k šh ›h œi ´ˆ µw µy ¶y ·| ¸p ¹l ºr ºs »j ¼j()Ljava/io/InputStream;()Ljava/lang/Runtime;()V(I)V(II)V*(Ljava/awt/Component;)Ljava/awt/Component;)(Ljava/awt/Component;Ljava/lang/Object;)V (Ljava/awt/Menu;)Ljava/awt/Menu;(Ljava/awt/MenuBar;)V((Ljava/awt/MenuItem;)Ljava/awt/MenuItem;.(Ljava/awt/TextArea;Ljava/io/BufferedReader;)VV(Ljava/awt/TextArea;Ljava/io/BufferedReader;Ljava/awt/TextField;Ljava/awt/TextField;)V"(Ljava/awt/event/ActionListener;)V(Ljava/io/InputStream;)V(Ljava/io/Reader;)V(Ljava/lang/Object;)V(Ljava/lang/Runnable;)V(Ljava/lang/String;)V(Ljava/lang/String;I)V(Ljava/lang/String;Z)V(Z)V(([Ljava/lang/String;)Ljava/lang/Process;([Ljava/lang/String;)VCenterCode ConstantValueDownload Speed = Error Messages ExceptionsExitLineNumberTableLjava/io/PrintStream;LocalVariablesMessages Left = No command to run, aborting NorthOptions SourceFileSouthStatus MessagesSuck Suck.javaSuckMenu SuckUpdateaddaddActionListenerbyebyeexecexitgetErrorStreamgetInputStream getRuntimejava/awt/BorderLayoutjava/awt/Componentjava/awt/Containerjava/awt/Framejava/awt/Label java/awt/Menujava/awt/MenuBarjava/awt/MenuItemjava/awt/Paneljava/awt/TextAreajava/awt/TextComponentjava/awt/TextFieldjava/awt/Windowjava/io/BufferedReaderjava/io/IOExceptionjava/io/InputStreamReaderjava/io/PrintStreamjava/lang/Objectjava/lang/Processjava/lang/Runtimejava/lang/Systemjava/lang/ThreadmainoutprintlnsetActionCommand setEditable setMenuBarsetSizesetUpshowstartsuck!" ³~àÜ*¾š²?¶A¸;»Y ·0L+Š,¶E»Y·*N»Y<·,:»Y ·1: - ¶5W-¶5W»Y·*:»Y<·,:»Y·1:  ¶5W¶5W»Y·*M»Y ·3:»Y·1: »Y ·3:»Y·1: ¶C¶C, ¶5W,¶5W, ¶5W,¶5W+,¶6+-¶6+ ¶6»Y·2: ¶B»Y ·4:¶8W»Y·':¶9»Y·):¶7W+¶D+¶H¸>:  *¶::» Y¶=·-:»Y·.:» Y¶<·-:»Y·.:»Y·(:»Y·(:¶G¶F»&Y·/:»&Y·/:¶I¶I±:²?¶@±IÐчê: $%%(-)9*D+K,R/[0g1r2z3‚6Š9—:¢;¯<º?À@ÆCÍDÔEÛFâIéJðKøOP STW'X.[7\?]E`IcIdNeVhdiok}lˆo‘pšs§u°w»xÆzË{ÐcÑ}Ó~Ûj*·+±‡Ž’suck-4.3.4/java/Suck.java000066400000000000000000000131521333033562000151600ustar00rootroot00000000000000import java.awt.*; import java.awt.event.*; import java.io.*; import java.lang.*; import java.text.*; public class Suck { public static void main(String[] args) { /* first declare all our variables */ Frame f; /* our master frame */ Panel ctl_pan, stat_pan, errs_pan; /* this panel will always stay on the screen */ TextArea stats, errs; /* where we display the messages and errors */ TextField ctl_count, ctl_speed; /* where we display count and BPS */ Label stat_lab, err_lab, ctl_count_label, ctl_speed_label; Button exit_button; /* how we exit our program */ Runtime rt; /* the runtime environment for our program */ Process prcs; /* the actual program we run */ InputStreamReader stdin, stderr; /* output from prg */ BufferedReader in_br, err_br; /* buffered output */ Thread std_thread, err_thread; /* threads to update TextAreas*/ Menu Option; /* our option menu */ MenuBar mb; /* the menu bar */ MenuItem menu_exit; /* My custom classes */ SuckMenu menu_action; SuckUpdate std_upd, err_upd; /* for our Threads */ /* sanity check our args */ if(args.length == 0) { System.out.println("No command to run, aborting\n"); System.exit(0); } /* set up our frame and size it */ f = new Frame("suck"); f.setSize(650,300); /* set up our panel for our status messages */ stat_pan = new Panel(); stats = new TextArea( 5,60 ); stat_lab = new Label("Status Messages"); stat_pan.add(stat_lab); stat_pan.add(stats); /* set up our panel for our error messages */ errs_pan = new Panel(); errs = new TextArea( 5,60); err_lab = new Label("Error Messages"); errs_pan.add(err_lab); errs_pan.add(errs); /* declare our panel for our controls */ ctl_pan = new Panel(); /* now set up our textfields for message count and BPS */ ctl_count = new TextField("",10); ctl_count_label = new Label("Messages Left = "); ctl_speed = new TextField("",10); ctl_speed_label = new Label("Download Speed = "); /* make em non-editable by user */ ctl_count.setEditable(false); ctl_speed.setEditable(false); /* add em to our frame */ ctl_pan.add(ctl_count_label); ctl_pan.add(ctl_count); ctl_pan.add(ctl_speed_label); ctl_pan.add(ctl_speed); /* now add em to our master frame */ f.add(ctl_pan,BorderLayout.NORTH); f.add(stat_pan,BorderLayout.CENTER); f.add(errs_pan,BorderLayout.SOUTH); /* set up our menu bar */ /* first, set up our menu item(s) */ menu_exit = new MenuItem("Exit"); /* this is the word we display */ menu_exit.setActionCommand("byebye"); /* this is the action */ /* now add em to our menu */ Option = new Menu("Options", true); Option.add(menu_exit); /* now set up our action */ menu_action = new SuckMenu(); Option.addActionListener(menu_action); /* now add the menu to our menu bar */ mb = new MenuBar(); mb.add(Option); f.setMenuBar(mb); /* all done setting up, show it */ f.show(); // now run our program try { rt = Runtime.getRuntime(); prcs = rt.exec(args); // get stdin first stdin = new InputStreamReader(prcs.getInputStream()); in_br = new BufferedReader(stdin); // now stderr stderr = new InputStreamReader(prcs.getErrorStream()); err_br = new BufferedReader(stderr); // Now set up and run our threads. std_upd = new SuckUpdate(); err_upd = new SuckUpdate(); /* set up the stats one to parse message count and BPS */ std_upd.setUp(stats, in_br, ctl_count, ctl_speed); /* error one doesn't parse message count and BPS */ err_upd.setUp(errs, err_br); std_thread = new Thread(std_upd); err_thread = new Thread(err_upd); std_thread.start(); err_thread.start(); } catch(IOException ioe) { System.out.println(ioe); } } } // following is an class to handle the menu class SuckMenu implements ActionListener { public void actionPerformed (ActionEvent e) { String s = e.getActionCommand(); if("byebye".equals(s)) { System.out.println("Exiting program."); System.exit(0); } } } // this class updates the status and message areas class SuckUpdate implements Runnable { TextArea Text; // area to update BufferedReader Data; // where we get our data from TextField MsgCount, BPS; /* if only called with these two we won't parse messagecount and BPS */ public void setUp(TextArea inp, BufferedReader br) { Text = inp; Data = br; MsgCount = null; BPS = null; } public void setUp(TextArea inp, BufferedReader br, TextField count, TextField bps) { Text = inp; Data = br; MsgCount = count; BPS = bps; } public void run() { String line; int count = 0; double bps = 0.0; Double dbl; int sep; DecimalFormat bpsf = new DecimalFormat( "#.0"); try { while((line = Data.readLine()) != null) { if(BPS != null && line.startsWith("---", 0)) { /* handle our message count and BPS lines */ /* the lines are formatted as follows*/ /* ---message_nr+++ BPS */ /* BPS may not be there */ sep = line.indexOf("+++"); try { count = Integer.parseInt(line.substring(3,sep)); if(sep+3 < line.length()) { /* get the BPS which is a double, and there's no direct way to get to a string */ dbl = Double.valueOf(line.substring(sep+3)); bps = dbl.doubleValue(); } } catch (NumberFormatException nfe) { System.out.println(line + ":" + nfe); System.out.println("sep = " + sep + " count =" + count + " bps=" +bps); } MsgCount.setText(String.valueOf(count)); BPS.setText(bpsf.format(bps) + " BPS"); } else { Text.append(line); Text.append("\n"); } } Data.close(); } catch (IOException ioe) { System.out.println(ioe); } } } suck-4.3.4/java/SuckMenu.class000066400000000000000000000013761333033562000161760ustar00rootroot00000000000000Êþº¾-8",')*012345        - . / 6$ 7()Ljava/lang/String;()V(I)V(Ljava/awt/event/ActionEvent;)V(Ljava/lang/Object;)Z(Ljava/lang/String;)VCode ConstantValue ExceptionsExiting program.LineNumberTableLjava/io/PrintStream;LocalVariables SourceFileSuck Suck.javaSuckMenu SuckUpdateactionPerformedbyebyeequalsexitgetActionCommandjava/awt/event/ActionEventjava/awt/event/ActionListenerjava/io/PrintStreamjava/lang/Objectjava/lang/Stringjava/lang/Systemoutprintln  +C+¶M,¶ ™²¶¸±#†ˆ‰Š…*· ±#ƒ&(suck-4.3.4/java/SuckUpdate.class000066400000000000000000000046401333033562000165110ustar00rootroot00000000000000Êþº¾-¦WXYZ[opqŸ€‚ƒŠ‹ŒŽ‘’“”•–—˜ : ; ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V r_ rm sz v{ ~z „y …a …c …h …l …m †_ ‡\ ˆ` ‰j ™] š| ›j œi œm ^  m ¢n £b £d ¤^ ¥b ¥g ¥k  BPS bps= count =#.0()D()I()Ljava/lang/String;()V(D)Ljava/lang/String;(D)Ljava/lang/StringBuffer;(I)Ljava/lang/String;(I)Ljava/lang/StringBuffer;(II)Ljava/lang/String;.(Ljava/awt/TextArea;Ljava/io/BufferedReader;)VV(Ljava/awt/TextArea;Ljava/io/BufferedReader;Ljava/awt/TextField;Ljava/awt/TextField;)V&(Ljava/lang/Object;)Ljava/lang/String;,(Ljava/lang/Object;)Ljava/lang/StringBuffer;(Ljava/lang/Object;)V(Ljava/lang/String;)I&(Ljava/lang/String;)Ljava/lang/Double;,(Ljava/lang/String;)Ljava/lang/StringBuffer;(Ljava/lang/String;)V(Ljava/lang/String;I)Z+++---:BPSCode ConstantValueData ExceptionsLineNumberTableLjava/awt/TextArea;Ljava/awt/TextField;Ljava/io/BufferedReader;Ljava/io/PrintStream;LocalVariablesMsgCount SourceFileSuck Suck.javaSuckMenu SuckUpdateTextappendclose doubleValueformatindexOfjava/awt/TextAreajava/awt/TextComponentjava/io/BufferedReaderjava/io/IOExceptionjava/io/PrintStreamjava/lang/Doublejava/lang/Integerjava/lang/NumberFormatExceptionjava/lang/Objectjava/lang/Runnablejava/lang/Stringjava/lang/StringBufferjava/lang/Systemjava/text/DecimalFormatjava/text/NumberFormatlengthoutparseIntprintlnreadLinerunsep = setTextsetUp startsWith substringtoStringvalueOf „yv{~zsz¡et=*+µ"*,µ *µ!*µ±x–— ˜™•¡ft>*+µ"*,µ *-µ!*µ±xœ žŸ›ž_t ý=J»Y·:§Ï*´Æ·+¶3™­+¶+6+¶5¸.=`+¶,¢b+`¶4¸9:¶)J§L:²-»Y+¸8·¶&¶%¶6¶0²-»Y ·¶$¶&¶$¶&)¶#¶6¶0*´!¸7¶2*´»Y)¶*¸8·¶&¶6¶2§*´"+¶'*´"¶'*´ ¶1YLÇÿ+*´ ¶(±:²-¶/±+TWñòxn£¤§©ª«#°+±+²6³AµN¶T±W¸Y¹wº ¼«½Ê«ÍÀÕÁÞªêÄñ©òÆôÇü¡r_t*·±xsuck-4.3.4/killfile.c000066400000000000000000001214601333033562000144300ustar00rootroot00000000000000#include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #ifdef HAVE_DIRENT_H # include #else # define dirent direct # ifdef HAVE_SYS_NDIR_H # include # endif # ifdef HAVE_SYS_DIR_H # include # endif #endif #ifdef HAVE_LIMITS_H #include #endif #include "suck_config.h" #ifdef HAVE_REGEX_H #include #endif #ifdef DMALLOC #include #endif #include "both.h" #include "suck.h" #include "suckutils.h" #include "killfile.h" #include "phrases.h" #include "timer.h" #define GROUP_KEEP "keep" #define GROUP_DELETE "delete" /* word in param file parsed for on group line to signify keep or delete */ #define NEWSGROUP_HEADER "Newsgroups: " /* header for line to match group files to */ /* local function prototypes */ int get_chunk_mem(PMaster, unsigned long *len, int which, char **buf); int check_newsgroups(char *, const char *); void free_node(OneKill); int parse_a_file(const char *, const char *, POneKill, int, int, int); int pass_two(PKillStruct, int, const char *fname); int check_a_group(PMaster, POneKill, char *, char **); void print_debug(PKillStruct, const char *); void debug_one_kill(POneKill); void add_to_linkedlist(pmy_regex *, pmy_regex); const char *strnstr(const char *, const char *); pmy_regex regex_scan(char *, char, int, int, char); int regex_check(char *, pmy_regex, int); void initialize_one_kill(POneKill); #ifdef HAVE_REGEX_H const char regex_chars[] = "*[]()^$\\?."; /* characters which make it a regex */ #endif void display_skiparray(unsigned char [SIZEOF_SKIPARRAY]); int find_string(int, pmy_regex, char *); /*-------------------------------------------------------------------------*/ /* the enum must match the phrases in suckengl.c killf_reasons[] */ enum { REASON_NONE, REASON_TOOMANYLINES, REASON_NOTENUFLINES, REASON_NRGRPS, REASON_NOKEEP, REASON_TIE, REASON_HEADER, \ REASON_BODY, REASON_BODYBIG, REASON_BODYSMALL, REASON_NRXREF }; enum { CHECK_EXACT, CHECK_CHECK }; enum { CHUNK_HEADER, CHUNK_BODY }; const struct { int len; const char *name; int headerlen; const char *header; } Params[] = { {8, "HILINES=", 7, "Lines: " }, {9, "LOWLINES=", 7, "Lines: "}, {7, "NRGRPS=", 12, "Newsgroups: "}, {6, "GROUP=", 0, ""}, {6, "QUOTE=", 0, ""}, {8, "PROGRAM=", 0, ""}, {7, "HEADER:", 0, ""}, {5, "BODY:", 0, ""}, {9, "BODYSIZE>", 0, ""}, {9, "BODYSIZE<", 0, ""}, {21, "GROUP_OVERRIDE_MASTER", 0, ""}, /* string in masterfile to signify that group files override the master */ {17, "TIEBREAKER_DELETE", 0, ""}, /* what to do if match multi groups override default of keep */ {18, "USE_EXTENDED_REGEX", 0, ""}, /* do we use extended regular expressions */ {10, "NON_REGEX=", 0, ""}, {5, "PERL=", 0, ""}, {14, "XOVER_LOG_LONG", 0, ""}, /* make Xover killlog look like regular kill lo)*/ {7, "NRXREF=", 6, "Xref: "}, }; enum { PARAM_HILINE, PARAM_LOWLINE, PARAM_NRGRPS, PARAM_GROUP, PARAM_QUOTE, PARAM_PROGRAM,PARAM_HDRSCAN, PARAM_BODY, PARAM_BODYBIG, PARAM_BODYSMALL, PARAM_GRPOVERRIDE, PARAM_TIEDELETE, PARAM_E_REGEX, PARAM_NONREGEX, PARAM_PERL, PARAM_XOVER_LOG_LONG, PARAM_NRXREF}; #define NR_PARAMS ((int) (sizeof(Params)/sizeof(Params[0]))) /*--------------------------------------------------------------------------*/ PKillStruct parse_killfile(int which, int logfile_yn, int debug, int ignore_postfix) { FILE *fptr; char buf[MAXLINLEN+1]; int i, doprg = FALSE, mastergrp = RETVAL_OK, retval = TRUE; const char *filename; #ifdef PERL_EMBED int doperl = FALSE; #endif KillStruct *Masterkill = NULL; /* kill file is going to get three passes, 1st one to count how many group files to process */ /* so can allocate memory for all the group stuff. */ /* also check for group override of masterkillfile */ /* and process PROGRAM line if it exists. If we have one we don't do anything else */ /* 2nd pass will be to actually process the group files */ /* 3rd pass will be to process the master delete stuff */ /* first malloc our master structure */ if((Masterkill = malloc(sizeof(KillStruct))) == NULL) { error_log(ERRLOG_REPORT, killf_phrases[1], NULL); retval = FALSE; } else { /* now initialize everything */ Masterkill->logfp = NULL; Masterkill->grp_override = FALSE; Masterkill->tie_delete = FALSE; Masterkill->totgrps = 0; Masterkill->grps = NULL; Masterkill->killfunc = chk_msg_kill; Masterkill->pbody= NULL; Masterkill->bodylen = 0; Masterkill->use_extended_regex = FALSE; Masterkill->xover_log_long = FALSE; /* initialize the master struct */ Masterkill->child.Stdin = Masterkill->child.Stdout = -1; Masterkill->child.Pid = -1; initialize_one_kill(&(Masterkill->master)); #ifdef PERL_EMBED Masterkill->perl_int = NULL; #endif Masterkill->logyn = logfile_yn; /* which option to we use in call to full_path() ? Do we use the postfix or not? */ Masterkill->ignore_postfix = ( ignore_postfix == TRUE) ? FP_GET_NOPOSTFIX : FP_GET ; filename = ( which == KILL_XOVER ) ? N_XOVER : N_KILLFILE; if(debug == TRUE) { do_debug("Trying to read killfile: %s\n", full_path(Masterkill->ignore_postfix, FP_DATADIR, filename)); } /* FIRST PASS THRU MASTER KILLFILE - look for group delete/keeps and count em and check for PROGRAM call*/ if((fptr = fopen(full_path(Masterkill->ignore_postfix, FP_DATADIR, filename), "r")) == NULL) { /* this is not really an error, so don't report it as such */ retval = FALSE; } else { if(debug == TRUE) { do_debug("Pass 1 & 2 kill file: %s\n", full_path(Masterkill->ignore_postfix, FP_DATADIR, filename)); } while(fgets(buf, MAXLINLEN, fptr) != NULL && doprg == FALSE) { /* nuke nl, so file names are correct, etc */ i = strlen(buf); if(buf[i-1] == '\n') { buf[i-1] = '\0'; } if(debug == TRUE) { do_debug("Read kill file line: %s\n", buf); } if(strncmp(buf, Params[PARAM_GROUP].name, (size_t) Params[PARAM_GROUP].len) == 0) { Masterkill->totgrps++; /* how many group files must we process */ } if(strncmp(buf, Params[PARAM_PROGRAM].name, (size_t) Params[PARAM_PROGRAM].len) == 0) { #ifdef PERL_EMBED if(Masterkill->perl_int == NULL) { #endif doprg = killprg_forkit(Masterkill, &buf[Params[PARAM_PROGRAM].len], debug, which); #ifdef PERL_EMBED } #endif } if(strncmp(buf, Params[PARAM_GRPOVERRIDE].name, (size_t) Params[PARAM_GRPOVERRIDE].len) == 0) { Masterkill->grp_override = TRUE; } if(strncmp(buf, Params[PARAM_TIEDELETE].name, (size_t) Params[PARAM_TIEDELETE].len) == 0) { Masterkill->tie_delete = TRUE; } if(strncmp(buf, Params[PARAM_E_REGEX].name, (size_t) Params[PARAM_E_REGEX].len) == 0) { Masterkill->use_extended_regex = TRUE; } if(strncmp(buf, Params[PARAM_XOVER_LOG_LONG].name, (size_t) Params[PARAM_XOVER_LOG_LONG].len) == 0) { Masterkill->xover_log_long = TRUE; } #ifdef PERL_EMBED if(strncmp(buf, Params[PARAM_PERL].name, (size_t) Params[PARAM_PERL].len) == 0) { if(doprg == FALSE) { doperl = killperl_setup(Masterkill, &buf[Params[PARAM_PERL].len], debug, which); } } #endif } (void) fclose(fptr); #ifndef PERL_EMBED if(doprg == TRUE) { #else if(doprg == TRUE || Masterkill->perl_int != NULL) { #endif Masterkill->totgrps = 0; } else { /* SECOND PASS - call routine */ if(Masterkill->totgrps > 0) { if(pass_two(Masterkill, debug, filename) == RETVAL_ERROR) { retval = FALSE; } } /* THIRD PASS - process master delete stuff */ if(retval != FALSE && (mastergrp = parse_a_file(filename, "Master", &(Masterkill->master), debug, Masterkill->ignore_postfix, Masterkill->use_extended_regex)) == RETVAL_ERROR) { retval = FALSE; } } } /* do we have an error OR do we have empty killfiles */ if((retval == FALSE) || (mastergrp == RETVAL_EMPTYKILL && Masterkill->totgrps == 0 && doprg == FALSE)) { #ifdef PERL_EMBED if(Masterkill->perl_int == NULL) { #endif free_killfile(Masterkill); /* just in case any memory got allocated */ Masterkill = NULL; #ifdef PERL_EMBED } #endif } else if(debug == TRUE) { print_debug(Masterkill, filename); } } return Masterkill; } /*-------------------------------------------------------------------------------------------*/ int pass_two(PKillStruct killp, int debug, const char *filename ) { int retval = RETVAL_OK; FILE *fptr; char buf[MAXLINLEN]; int grpon = 0; size_t i; char *grpname, *grpfile, *delkeep; grpname = grpfile = delkeep = NULL; /* SECOND PASS - now that we know how many, we can allocate the space for em */ /* and then have parse_a_file read em in. */ if((killp->grps = calloc((size_t) killp->totgrps, sizeof(Group))) == NULL) { retval = RETVAL_ERROR; error_log(ERRLOG_REPORT, killf_phrases[1], NULL); } else if((fptr = fopen(full_path(killp->ignore_postfix, FP_DATADIR, filename), "r")) == NULL) { MyPerror(full_path(killp->ignore_postfix, FP_DATADIR, filename)); retval = RETVAL_ERROR; } else { while(retval == RETVAL_OK && fgets(buf, MAXLINLEN, fptr) != NULL) { if(strncmp(buf, Params[PARAM_GROUP].name, (size_t) Params[PARAM_GROUP].len) == 0 ) { /* now parse the line for the 3 required elements */ /* keep/delete group_name filename */ delkeep = &buf[Params[PARAM_GROUP].len]; if(strncmp(delkeep, GROUP_KEEP, (size_t) strlen(GROUP_KEEP)) == 0) { killp->grps[grpon].delkeep = DELKEEP_KEEP; } else if(strncmp(delkeep, GROUP_DELETE, (size_t) strlen(GROUP_DELETE)) == 0) { killp->grps[grpon].delkeep = DELKEEP_DELETE; } else { retval = RETVAL_ERROR; } if(retval == RETVAL_OK) { grpname = strchr(delkeep, ' '); /* find the space */ if(grpname == NULL) { retval = RETVAL_ERROR; } else { ++grpname; /* move past space */ grpfile = strchr(grpname, ' '); if(grpfile == NULL) { retval = RETVAL_ERROR; } else { *grpfile = '\0'; /* truncate the group name for easier copying later */ ++grpfile; /* nuke newline */ i = strlen(grpfile) - 1; if(grpfile[i] == '\n') { grpfile[i] = '\0'; } } } } if(retval == RETVAL_ERROR) { error_log(ERRLOG_REPORT, killf_phrases[2], buf, NULL); } else { /* have all three params, put them in place and parse the file */ /* +1 for newline */ if((killp->grps[grpon].group = malloc(strlen(grpname)+1)) == NULL) { error_log(ERRLOG_REPORT, killf_phrases[0], NULL); retval = RETVAL_ERROR; } else { strcpy(killp->grps[grpon].group, grpname); /* get, ignoring postfix, so use absolute filename on group line */ if(parse_a_file(grpfile, grpname, &(killp->grps[grpon].match), debug,FP_GET_NOPOSTFIX,killp->use_extended_regex) != RETVAL_OK) { /* whoops couldn't open file, ignore */ free(killp->grps[grpon].group); grpon--; /* so that we reuse this grp entry */ killp->totgrps--; } } } grpon++; /* finished with this group */ } } (void) fclose(fptr); } return retval; } /*--------------------------------------------------------------------------*/ void free_killfile(PKillStruct master) { int i; if(master != NULL) { /* first kill off killprg if its there */ if(master->killfunc==chk_msg_kill_fork || master->child.Pid != -1) { killprg_closeit(master); } #ifdef PERL_EMBED if(master->perl_int != NULL) { killperl_done(master); } #endif /* close off log, if opened */ if(master->logfp != NULL) { fclose(master->logfp); } free_node(master->master); if(master->totgrps > 0) { for(i=0;itotgrps;i++) { free_node(master->grps[i].match); free(master->grps[i].group); } free(master->grps); } free(master); } } /*--------------------------------------------------------------------*/ void free_node(OneKill node) { pmy_regex curr, next; curr = node.list; if (node.header != NULL) { #ifdef HAVE_REGEX_H if(node.header->ptrs != NULL) { regfree(node.header->ptrs); } #endif if(node.header->header != NULL) { free(node.header->header); } if(node.header->string != NULL) { free(node.header->string); } } while(curr != NULL) { #ifdef HAVE_REGEX_H if(curr->ptrs != NULL) { regfree(curr->ptrs); } #endif if(curr->header != NULL) { free(curr->header); } if(curr->string != NULL) { free(curr->string); } next = curr->next; free(curr); curr = next; } } /*--------------------------------------------------------------------------*/ int get_one_article_kill(PMaster master, int logcount, long itemon) { char buf[MAXLINLEN+1], *inbuf; const char *tname; char fname[PATH_MAX+1]; int retval, x; unsigned long len; FILE *fptr; PKillStruct killp; killp = master->killp; retval = RETVAL_OK; killp->pbody = NULL; /* since we haven't downloaded anything yet for this article */ killp->bodylen = 0; retval =get_chunk_mem(master, &len, CHUNK_HEADER, &inbuf); /* the killfunc pointer points to either chk_msg_kill(), chk_msg_kill_fork(), or chk_msg_kill_perl() */ /* do we have to download this sucker, is it mandatory? */ if(retval == RETVAL_OK && ((master->curr)->mandatory == MANDATORY_YES || (*killp->killfunc)(master, killp, inbuf, len) == FALSE)) { if(master->MultiFile == TRUE) { /* open file */ /* file name will be ####-#### ex 001-166 (nron,total) */ sprintf(buf,"%0*ld-%d", logcount, itemon, master->nritems); /* the strcpy to avoid wiping out fname in second call to full_path */ strcpy(fname, full_path(FP_GET, FP_MSGDIR, buf)); strcat(buf, N_TMP_EXTENSION); /* add temp file extension */ tname = full_path(FP_GET, FP_TMPDIR, buf); /* temp file name */ if(master->debug == TRUE) { do_debug("File name = \"%s\" temp = \"%s\"", fname, tname); } if((fptr = fopen(tname, "w")) == NULL) { MyPerror(tname); retval = RETVAL_ERROR; } else { /* write the header */ x = fwrite(inbuf,sizeof(inbuf[0]),len, fptr); fputs("\n", fptr); /* needed */ if(x != len) { retval = RETVAL_ERROR; error_log(ERRLOG_REPORT, killf_phrases[9], NULL); } if(retval == RETVAL_OK && master->header_only == FALSE) { /* have we already downloaded the body? */ if(killp->pbody != NULL) { inbuf = killp->pbody; len = killp->bodylen; } else { retval = get_chunk_mem(master, &len, CHUNK_BODY, &inbuf); } if(retval == RETVAL_OK) { x = fwrite(inbuf, sizeof(inbuf[0]), len, fptr); if(x != len) { retval = RETVAL_ERROR; error_log(ERRLOG_REPORT, killf_phrases[9], NULL); } } } (void) fclose(fptr); if(retval != RETVAL_OK) { unlink(tname); } /* now rename it to the permanent file name */ else { move_file(tname, fname); if((master->batch == BATCH_LIHAVE || master->batch == BATCH_INNFEED) && master->innfeed != NULL) { /* write path name and msgid to file */ fprintf(master->innfeed, "%s %s\n", fname, (master->curr)->msgnr); fflush(master->innfeed); /* so it gets written sooner */ } } } } else { fputs(inbuf, stdout); fputs("\n", stdout); if(master->header_only == FALSE) { retval = get_chunk_mem(master, &len, CHUNK_BODY, &inbuf); if(retval == RETVAL_OK) { fwrite(inbuf, sizeof(inbuf[0]), len, stdout); } } /* this is needed as a separator in stdout version */ fputs(".\n", stdout); } if(retval == RETVAL_OK) { master->nrgot++; } } if(retval == RETVAL_UNEXPECTEDANS) { retval = RETVAL_OK; /* so don't abort */ } return retval; } /*---------------------------------------------------------------*/ /* this routine gets the header or body into memory, keeping separate buffers for each */ int get_chunk_mem(PMaster master, unsigned long *size, int which, char **retbuf) { static char *header_buf = NULL; static char *body_buf = NULL; static int header_size = 8192; static unsigned long body_size = KILL_BODY_BUF_SIZE; int done, partial, len, i, retval; char *inbuf, *newbuf, *buf; const char *cmd; unsigned long temp, bufsize, currbuf; /* bufsize = alloced memory, currbuf = what retrieved so far */ done = FALSE; partial = FALSE; currbuf = 0; retval = RETVAL_OK; if(which == CHUNK_HEADER) { buf = header_buf; bufsize = header_size; } else { buf = body_buf; bufsize = body_size; } if(buf == NULL) { if((buf=malloc((size_t) bufsize)) == NULL) { error_log(ERRLOG_REPORT, killf_phrases[0], NULL); retval = RETVAL_ERROR; } } if(buf != NULL) { /* build command */ if(which == CHUNK_HEADER) { cmd = build_command(master, "head", master->curr); i = 221; } else { cmd = build_command(master, "body", master->curr); i = 222; } if((retval = send_command(master, cmd, NULL, i)) != RETVAL_OK) { free(buf); buf = NULL; } } while(buf != NULL && done == FALSE) { len=sgetline(master->sockfd, &inbuf, master->do_ssl, master->ssl_struct); (void) TimerFunc(TIMER_ADDBYTES, len, NULL); if(len < 0) { free(buf); buf = NULL; done = TRUE; retval = RETVAL_ERROR; } else if(partial == FALSE && inbuf[0] == '.') { if(len == 2 && inbuf[1] == '\n') { done = TRUE; } else { /* handle double dots IAW RFC977 2.4.1*/ inbuf++; /* move past first dot */ len--; } } if(done == FALSE) { while((len+currbuf) > bufsize && buf != NULL) { /* buffer not big enough realloc */ /* how much do we increase buf */ /* we do this test so in case KILL_CHUNK_BUF_INCREASE < len, we */ /* don't get a buffer overflow */ temp = (len > KILL_CHUNK_BUF_INCREASE) ? len : KILL_CHUNK_BUF_INCREASE; if(master->debug == TRUE) { do_debug("Re-allocing buffer from %lu to %lu\n", bufsize, bufsize+temp); } bufsize += temp; if((newbuf = realloc(buf, (size_t) bufsize)) == NULL) { free(buf); buf = NULL; currbuf = 0; error_log(ERRLOG_REPORT, killf_phrases[0], NULL); retval = RETVAL_ERROR; } else { buf = newbuf; } } if(buf != NULL) { /* put string in buffer, use memmove in case of nulls*/ memmove(buf+currbuf, inbuf, len); currbuf += len; partial= (len==MAXLINLEN&&inbuf[len-1]!='\n') ? TRUE : FALSE; } } } /* now save the values for next time */ if(which == CHUNK_HEADER) { header_buf = buf; header_size = bufsize; } else { body_buf = buf; body_size = bufsize; } /* return the length */ *size = currbuf; /* make sure buf is NULL terminated */ /* this is needed so logging is correct */ /* since using memmove() above, we don't copy the ending NULL */ if(buf != NULL) { buf[currbuf] = '\0'; } *retbuf = buf; return retval; } /*-------------------------------------------------------------------------*/ /* chk_msg_kill - return TRUE if kill article, FALSE if keep */ /* if kill article, add it to killlog */ int chk_msg_kill(PMaster master, PKillStruct killp, char *headerbuf, int buflen) { int killyn, i, del, keep, match, masterkill; const char *group = "Master"; char *why, *goodwhy; goodwhy = why = killf_reasons[REASON_NONE]; killyn = FALSE; /* first check against master delete */ masterkill = check_a_group(master, &(killp->master), headerbuf, &why); if(masterkill == TRUE && killp->grp_override == FALSE) { killyn = masterkill; } else { /* okay now have to parse group line */ /* then check to see if I have group keep/deletes for each group */ /* default actions */ keep = FALSE; del = FALSE; for(i=0;itotgrps;i++) { if(check_newsgroups(headerbuf, killp->grps[i].group) == TRUE) { /* bingo this article matches one of our group check it */ match = check_a_group(master, &(killp->grps[i].match), headerbuf, &why); if(killp->grps[i].delkeep == DELKEEP_KEEP) { /* matched keep group */ if(match == TRUE) { keep = TRUE; } else { del = TRUE; group = killp->grps[i].group; goodwhy = killf_reasons[REASON_NOKEEP]; } } else { if(match == TRUE) { del = TRUE; goodwhy = why; group = killp->grps[i].group; } else { keep = TRUE; } } } } /* now determine if we kill or keep this sucker */ if(keep == FALSE && del == FALSE) { /* no group matches, do what masterkill says to do */ killyn = masterkill; } else if(keep != del) { /* only matched one group, figure out which */ killyn = ( del == TRUE) ? TRUE : FALSE; why = goodwhy; } else { /* matched both, use TIEBREAKER */ why = killf_reasons[REASON_TIE]; killyn = killp->tie_delete; } } if(master->debug == TRUE && killyn == TRUE) { do_debug("killing: %s: %s: %s", group, why, headerbuf); } if(killyn == TRUE && killp->logyn != KILL_LOG_NONE) { /* log it */ /* only open this sucker once */ if(killp->logfp == NULL) { if((killp->logfp = fopen(full_path(FP_GET, FP_TMPDIR, master->kill_log_name), "a")) == NULL) { MyPerror(killf_phrases[3]); } } if(killp->logfp != NULL) { /* first print our one-line reason */ print_phrases(killp->logfp,killf_phrases[4], group ,why, (master->curr)->msgnr, NULL); if(killp->logyn == KILL_LOG_LONG) { /* print the header as well */ /* the nl so I get a blank line between em */ print_phrases(killp->logfp, "%v1%\n", headerbuf, NULL); } } } return killyn; } /*-----------------------------------------------------------------------*/ int check_a_group(PMaster master, POneKill killp, char *headerbuf, char **why) { int i, match = FALSE; char *startline, *tptr; pmy_regex curr; static char reason[MAXLINLEN]; PKillStruct temp; /* check hilines first */ if(killp->hilines > 0) { if((startline = strstr(headerbuf, Params[PARAM_HILINE].header)) != NULL) { i = 0; /* just in case */ sscanf(startline+Params[PARAM_HILINE].headerlen, "%d", &i); if(killp->hilines < i) { /* JACKPOT */ match = TRUE; *why = killf_reasons[REASON_TOOMANYLINES]; } } } /* now check low lines */ if(match == FALSE && killp->lowlines > 0) { if((startline = strstr(headerbuf, Params[PARAM_LOWLINE].header)) != NULL) { i = 0; /* just in case */ sscanf(startline+Params[PARAM_LOWLINE].headerlen, "%d", &i); if(i < killp->lowlines) { /* JACKPOT */ match = TRUE; *why = killf_reasons[REASON_NOTENUFLINES]; } } } /* now check nrgrps */ if(match == FALSE && killp->maxgrps > 0) { if((startline = strstr(headerbuf, Params[PARAM_NRGRPS].header)) != NULL) { /* count the nr of commas in the group line */ i = 1; /* have at least one group */ tptr = startline; while(i <= killp->maxgrps && *tptr != '\n' && *tptr != '\0' ) { /* some news server use space vice comma for separator */ if(*tptr == COMMA || *tptr == SPACE ) { i++; } tptr++; } if(i > killp->maxgrps) { match = TRUE; *why = killf_reasons[REASON_NRGRPS]; } } } /* now check nrxref */ if(match == FALSE && killp->maxxref > 0) { if((startline = strstr(headerbuf, Params[PARAM_NRXREF].header)) != NULL) { /* count the nr of colons in line, they separate the group/article nr */ i = 0; tptr = startline; while(i <= killp->maxxref && *tptr != '\n' && *tptr != '\0' ) { if(*tptr == COLON) { i++; } tptr++; } if(i > killp->maxxref) { match = TRUE; *why = killf_reasons[REASON_NRXREF]; } } } curr = killp->list; while(match == FALSE && curr != NULL) { if(regex_check(headerbuf, curr, master->debug) == TRUE) { match = TRUE; sprintf(reason, "%s %s %s ", killf_reasons[REASON_HEADER], curr->header, curr->string); *why = reason; } curr = curr->next; } curr = killp->header; while(match == FALSE && curr != NULL) { /* scan the entire header */ if(regex_block(headerbuf, curr, master->debug) == TRUE) { match = TRUE; sprintf(reason,"%s %s", killf_reasons[REASON_HEADER], curr->string); *why = reason; } curr = curr->next; } if(match == FALSE && (killp->bodybig > 0 || killp->bodysmall > 0 || killp->body != NULL)) { /* may have to download the header first to */ /* have a pointer to the master killstruct */ temp = master->killp; if(temp->pbody == NULL) { /* have to get the body first */ get_chunk_mem(master, &(temp->bodylen), CHUNK_BODY, &(temp->pbody)); } if(killp->bodybig > 0 && temp->bodylen > killp->bodybig) { match = TRUE; sprintf(reason, "%s %lu", killf_reasons[REASON_BODYBIG], temp->bodylen); *why = reason; } else if(killp->bodysmall > 0 && temp->bodylen < killp->bodysmall) { match = TRUE; sprintf(reason, "%s %lu", killf_reasons[REASON_BODYSMALL], temp->bodylen); *why = reason; } else { curr = killp->body; while(match == FALSE && curr != NULL) { /* scan the body */ if(regex_block(temp->pbody, curr, master->debug) == TRUE) { match = TRUE; sprintf(reason, "%s %s", killf_reasons[REASON_BODY], curr->string); *why = reason; } curr = curr->next; } } } return match; } /*--------------------------------------------------------------------------*/ int check_newsgroups(char *header, const char *whichgroup) { /* search Newsgroup headerline for whichgroup */ /* if in return TRUE else return FALSE; */ int match = FALSE; char *startline, *ptr; const char *ptr_group; if((startline = strstr(header, NEWSGROUP_HEADER)) != NULL) { ptr = startline + strlen(NEWSGROUP_HEADER); while( match == FALSE && *ptr != '\0' && *ptr != '\n' ) { ptr_group = whichgroup; while( *ptr == *ptr_group && *ptr != '\0') { ptr++; ptr_group++; } if(*ptr_group == '*') { /* wildcard match, they match so far, so they match */ match = TRUE; } else if((*ptr_group == '\0') && (*ptr == COMMA || *ptr == ' ' || *ptr == '\n' || *ptr == '\0')) { /* if we are at the end of both group names then we have a match */ /* we check for space even though its not in the standard, apparently */ /* some news server use it. */ match = TRUE; } else { /* advance to next group on line */ while(*ptr != COMMA && *ptr != '\n' && *ptr != '\0') { ptr++; } if(*ptr == COMMA) { /* advance past it */ ptr++; } } } } return match; } /*-------------------------------------------------------------------------------*/ void initialize_one_kill(POneKill mykill) { /* initialize the various elements*/ mykill->list = NULL; mykill->header = NULL; mykill->body = NULL; mykill->hilines = mykill->lowlines = mykill->maxgrps = mykill->maxxref = 0; mykill->bodybig = mykill->bodysmall = 0; mykill->quote = KILLFILE_QUOTE; mykill->non_regex = KILLFILE_NONREGEX; return; } /*-------------------------------------------------------------------------------*/ int parse_a_file(const char *fname, const char *group, POneKill mykill, int debug, int ignore_prefix, int use_e_regex) { FILE *fptr; char buf[MAXLINLEN+1]; int i, match; int retval = RETVAL_OK; pmy_regex curr; /* first initialize the killstruct */ initialize_one_kill(mykill); #ifdef HAVE_REGEX_H mykill->use_extended = use_e_regex; #endif /* now read in the killfile and parse it */ if((fptr = fopen(full_path(ignore_prefix, FP_DATADIR, fname), "r")) == NULL) { MyPerror(full_path(ignore_prefix, FP_DATADIR, fname)); retval = RETVAL_ERROR; } else { if(debug == TRUE) { do_debug("Pass 3 kill file: %s\n", full_path(ignore_prefix, FP_DATADIR, fname)); do_debug("Pass 3 options: group=%s, use_extended_regex=%s, ignore_prefix=%s\n", group, true_str(use_e_regex), true_str(ignore_prefix)); } while(fgets(buf, MAXLINLEN, fptr) != NULL) { buf[MAXLINLEN] = '\0'; /* just in case */ i = strlen(buf); /* strip nls off so they don't get added to regex scan, etc..*/ if(buf[i-1] == '\n') { buf[i-1] = '\0'; } if(debug == TRUE) { do_debug("pass 3 kill file line: %s\n", buf); } if(buf[0] != KILLFILE_COMMENT_CHAR) { /* skip any comment lines */ match = FALSE; for(i = 0 ; i < NR_PARAMS; i++) { if(strncmp(buf, Params[i].name, (size_t) Params[i].len) == 0) { match = TRUE; /* so don't try header field scan later */ switch(i) { case PARAM_HILINE: (void) sscanf(&buf[Params[PARAM_HILINE].len], "%d", &(mykill->hilines)); break; case PARAM_LOWLINE: (void) sscanf(&buf[Params[PARAM_LOWLINE].len], "%d", &(mykill->lowlines)); break; case PARAM_NRGRPS: (void) sscanf(&buf[Params[PARAM_NRGRPS].len], "%d", &(mykill->maxgrps)); break; case PARAM_NRXREF: (void) sscanf(&buf[Params[PARAM_NRXREF].len], "%d", &(mykill->maxxref)); break; case PARAM_QUOTE: if(buf[Params[PARAM_QUOTE].len] == '\0') { error_log(ERRLOG_REPORT, "%s\n", killf_phrases[6], NULL); } else { mykill->quote = buf[Params[PARAM_QUOTE].len]; } break; case PARAM_NONREGEX: if(buf[Params[PARAM_NONREGEX].len] == '\0') { error_log(ERRLOG_REPORT, "%s\n", killf_phrases[12], NULL); } else { mykill->non_regex = buf[Params[PARAM_NONREGEX].len]; } break; case PARAM_HDRSCAN: if(buf[Params[PARAM_HDRSCAN].len] == '\0') { error_log(ERRLOG_REPORT, "%s\n", killf_phrases[7], NULL); } else { if((curr = regex_scan(buf, mykill->quote, debug, use_e_regex, mykill->non_regex)) != NULL) { add_to_linkedlist(&(mykill->header), curr); } } break; case PARAM_BODY: if(buf[Params[PARAM_BODY].len] == '\0') { error_log(ERRLOG_REPORT, "%s\n", killf_phrases[10], NULL); } else { if((curr = regex_scan(buf, mykill->quote, debug, use_e_regex, mykill->non_regex)) != NULL) { add_to_linkedlist(&(mykill->body), curr); } } break; case PARAM_BODYBIG: (void) sscanf(&buf[Params[PARAM_BODYBIG].len], "%lu", &(mykill->bodybig)); break; case PARAM_BODYSMALL: (void) sscanf(&buf[Params[PARAM_BODYSMALL].len], "%lu", &(mykill->bodysmall)); break; case PARAM_E_REGEX: /* in case specified for this file only */ use_e_regex = TRUE; #ifdef HAVE_REGEX_H mykill->use_extended = TRUE; /* save for printing in debug */ #endif break; default: /* handle all other lines aka do nothing*/ break; } } } if(match == FALSE) { /* it's a different line in the header add to linked-list */ if((curr = regex_scan(buf, mykill->quote, debug, use_e_regex, mykill->non_regex)) != NULL) { add_to_linkedlist(&(mykill->list), curr); } } } /* comment line stuff */ } (void) fclose(fptr); /* check for an empty killfile */ if(mykill->hilines == 0 && mykill->lowlines == 0 && mykill->maxgrps == 0 && mykill->bodybig == 0 && mykill->bodysmall == 0 && mykill->header == NULL && mykill->list == NULL && mykill->body == NULL && mykill->maxxref == 0) { retval = RETVAL_EMPTYKILL; } } return retval; } /*---------------------------------------------------------------------------------------------*/ void add_to_linkedlist(pmy_regex *head_ptr, pmy_regex addon) { /* this routine adds a pointer the end of a linked list */ pmy_regex curr; if(*head_ptr == NULL) { /* put on top of list */ *head_ptr = addon; } else { curr = *head_ptr; while(curr->next != NULL) { curr = curr->next; } curr->next = addon; } } /*----------------------------------------------------------------------------------*/ /* scan a line and build the compiled regex pointers to call regexex() with */ /* ---------------------------------------------------------------------------------*/ pmy_regex regex_scan(char *linein, char quotechar, int debug, int use_e_regex, char non_regex) { char *tptr, *startline; int len,x,y, case_sens = FALSE, flags = FALSE; #ifdef HAVE_REGEX_H char errmsg[256]; int i,j, useregex = TRUE; #endif pmy_regex which = NULL; int err = FALSE; if(linein != NULL) { /* first find the : in the line to skip the parameter part */ tptr = linein; while(*tptr != '\0' && *tptr != ':') { tptr++; } startline = tptr+1; len = startline - linein; /* do we treat this string as a non-regex?? */ if(*startline == non_regex) { startline++; #ifdef HAVE_REGEX_H useregex = FALSE; #endif } /* case sensitive search?? */ if(*startline == quotechar) { startline++; case_sens = TRUE; } #ifdef HAVE_REGEX_H if(useregex == TRUE) { flags = REG_NOSUB; if(case_sens == FALSE) { flags |= REG_ICASE; } if(use_e_regex == TRUE) { flags |= REG_EXTENDED; } useregex = FALSE; /* test to see whether or not we need to use regex */ for(i = 0; i < strlen(regex_chars) && useregex != TRUE; i++) { for(j = 0 ; j < strlen(startline)&& useregex != TRUE; j++) { if(startline[j] == regex_chars[i]) { useregex = TRUE; } } } } #endif /* now count each of em, so that can alloc my array of regex_t */ if(*tptr != '\0') { /* now alloc memory */ if((which = malloc(sizeof (my_regex))) == NULL) { error_log(ERRLOG_REPORT, killf_phrases[5], NULL); } else { /* first save the string for later printing, if necessary */ if((which->string = malloc(strlen(startline)+1)) == NULL) { err = TRUE; error_log(ERRLOG_REPORT, killf_phrases[5], NULL); } else { strcpy(which->string,startline); which->next = NULL; /* initialize */ which->case_sensitive = case_sens; #ifdef HAVE_REGEX_H which->ptrs = NULL; /* just to be on safe side */ #endif if((which->header=calloc(sizeof(char), len+1)) == NULL) { error_log(ERRLOG_REPORT, killf_phrases[5], NULL); err = TRUE; } else { strncpy(which->header, linein, len); which->header[len] = '\0'; /* just in case */ #ifdef HAVE_REGEX_H if(useregex == TRUE) { if((which->ptrs = malloc(sizeof(regex_t))) == NULL) { error_log(ERRLOG_REPORT, killf_phrases[5], NULL); } else { if(debug == TRUE) { do_debug("Regcomping -%s-\n", startline); } if((err = regcomp(which->ptrs, startline, flags)) != 0) { /* whoops */ regerror(err, which->ptrs, errmsg, sizeof(errmsg)); error_log(ERRLOG_REPORT, killf_phrases[11], startline, errmsg, NULL); err= TRUE; } } } else { #endif /* not using regex, fill in skiparray */ /* first fill in max skip for all chars (len of string) */ /* See Algorithms/Sedgewick pg 287-289 for explanation */ y=strlen(which->string); for(x=0;xskiparray);x++) { which->skiparray[x] = y; } /* now go back and fill in those in our search string */ if(case_sens == FALSE) { /* case-insensitive search */ for(x=0;xstring[x]; which->skiparray[(unsigned char) toupper(xx)] = (y-1)-x; which->skiparray[(unsigned char) tolower(xx)] = (y-1)-x; } } else { for(x=1;xskiparray[(unsigned char) which->string[x]] = (y-1)-x; } } #ifdef HAVE_REGEX_H } #endif } } } } } if(which != NULL && err == TRUE) { /* error, free up everything */ #ifdef HAVE_REGEX_H if(which->ptrs != NULL) { free(which->ptrs); } #endif if(which->string != NULL) { free(which->string); } if(which->header != NULL) { free(which->header); } if(which != NULL) { free(which); which = NULL; } } return which; } /*------------------------------------------------------------------------------------*/ int regex_check(char *headerbuf, pmy_regex expr, int debug) { int match = FALSE; char *startline, *endline; if(expr->header != NULL) { if((startline = strstr(headerbuf, expr->header)) != NULL) { endline = strchr(startline, '\n'); /* end this line, so we only search the right header line */ if(endline != NULL) { *endline = '\0'; } /* the +strlen(expr->header) so the header field and the space */ /* immediately following it aren't included in the search */ if(debug == TRUE) { do_debug("checking -%s- for -%s-\n", startline+strlen(expr->header)+1, expr->string); } #ifdef HAVE_REGEX_H if(expr->ptrs != NULL) { if(regexec(expr->ptrs, startline+strlen(expr->header)+1, 0, NULL, 0) == 0) { match = TRUE; } } else { #endif match = find_string(debug, expr, startline+strlen(expr->header)+1); #ifdef HAVE_REGEX_H } #endif /* put the nl back on that we deleted */ if(endline != NULL) { *endline = '\n'; } } } if(debug== TRUE) { do_debug("Returning match=%s\n", (match == TRUE) ? "True" : "False"); } return match; } /*------------------------------------------------------------------------------------*/ int regex_block(char *what, pmy_regex where, int debug) { /* find a string in a block */ int match = FALSE; if(debug == TRUE && where->string != NULL) { do_debug("Checking for -%s-\n", where->string); } #ifdef HAVE_REGEX_H if(where->ptrs != NULL) { if(regexec(where->ptrs, what, 0, NULL, 0) == 0) { match = TRUE; } } else { #endif match = find_string(debug, where, what); #ifdef HAVE_REGEX_H } #endif return match; } /*---------------------------------------------------------------------------------------------*/ int find_string(int debug, pmy_regex expr, char *str) { /* the search mechanism is based on the Boyer-Moor Algorithms (pg 286- of Algorithms book) */ int match = FALSE; char *ptr; /* pointer to pattern */ int lenp; /* length of pattern */ int lens; /* length of string we are searching */ int i; /* current location in string */ int j; /* current location in pattern */ int t; /* value of skip array for string location */ ptr = expr->string; lenp = strlen(ptr); lens = strlen(str); if(expr->case_sensitive == TRUE ) { /* do case-sensitive search */ if(debug == TRUE) { do_debug("Case-sensitive search for %s\n", ptr); } i = j = lenp-1; do { if(str[i] == ptr[j]) { i--; j--; } else { t = expr->skiparray[(unsigned char) str[i]]; i += ( lenp -j > t) ? (lenp - j) : t; j = lenp - 1; } } while( j >= 0 && i < lens); if(j < 0) { match = TRUE; } } else { /* do case-insensitive search */ if(debug == TRUE ) { do_debug("Case-insensitive search for --%s--\n", ptr); } i = j = lenp-1; do { if(tolower(str[i]) == tolower(ptr[j])) { i--; j--; } else { t = expr->skiparray[(unsigned char) str[i]]; i += ( lenp -j > t) ? (lenp - j) : t; j = lenp - 1; } } while( j >= 0 && i < lens); if(j < 0) { match = TRUE; } } return match; } /*------------------------------------------------------------------------------------*/ void print_debug(PKillStruct master, const char *fname) { int i; /* print out status of master killstruct to debug file */ do_debug("--%s-- Master KillStruct\n",fname); do_debug("logyn=%s\n",true_str(master->logyn)); do_debug("grp_override=%s\n",true_str(master->grp_override)); do_debug("tie_delete=%s\n",true_str(master->tie_delete)); do_debug("totgrps=%d\n", master->totgrps); do_debug("ignore_postfix=%s\n",true_str(master->ignore_postfix)); do_debug("use_extended_regex=%s\n", true_str(master->use_extended_regex)); do_debug("xover_long_long=%s\n", true_str(master->xover_log_long)); do_debug("Master kill group"); debug_one_kill(&(master->master)); for(i = 0 ; i < master->totgrps ; i++ ) { do_debug("Group %d = %s\n", i, master->grps[i].group); do_debug("--delkeep =%s\n", (master->grps[i].delkeep == DELKEEP_KEEP) ? "keep" : "delete"); debug_one_kill(&(master->grps[i].match)); } do_debug("--%s-- End of Killstruct\n", fname); } /*------------------------------------------------------------------------*/ void debug_one_kill(POneKill ptr) { pmy_regex temp; do_debug("--hilines =%d\n", ptr->hilines); do_debug("--lowlines=%d\n", ptr->lowlines); do_debug("--maxgrps=%d\n", ptr->maxgrps); do_debug("--maxxref=%d\n", ptr->maxxref); do_debug("--bodysize>%lu\n", ptr->bodybig); do_debug("--bodysize<%lu\n", ptr->bodysmall); do_debug("--quote=%c\n", ptr->quote); do_debug("--non_regex=%c\n", ptr->non_regex); #ifdef HAVE_REGEX_H do_debug("--use_extended_regex=%s\n", true_str(ptr->use_extended)); #endif temp= ptr->header; while(temp != NULL) { #ifdef HAVE_REGEX_H do_debug("--header scan is %sregex\n", (temp->ptrs == NULL) ? "non-" : ""); #endif do_debug("--Scan is %scase-sensitive\n", (temp->case_sensitive == TRUE) ? "" : "non-"); if(temp->string != NULL) { do_debug("--header scan=%s\n", temp->string); } temp = temp->next; } temp = ptr->body; while(temp != NULL) { #ifdef HAVE_REGEX_H do_debug("--body scan is %sregex\n", (temp->ptrs == NULL) ? "non-" : ""); #endif do_debug("--Scan is %scase-sensitive\n", (temp->case_sensitive == TRUE) ? "" : "non-"); if(temp->string != NULL) { do_debug("--body scan=%s\n", temp->string); } temp = temp->next; } temp = ptr->list; while(temp != NULL) { #ifdef HAVE_REGEX_H do_debug("--header match is %sregex\n", (temp->ptrs == NULL) ? "non-" : ""); #endif do_debug("--Scan is %scase-sensitive\n", (temp->case_sensitive == TRUE) ? "" : "non-"); do_debug("--header match= %s %s\n", temp->header, temp->string); temp = temp->next; } do_debug("--end of Killfile Group--\n"); } suck-4.3.4/killfile.h000066400000000000000000000063251333033562000144370ustar00rootroot00000000000000#ifndef _SUCK_KILLFILE_H #define _SUCK_KILLFILE_H 1 #define SIZEOF_SKIPARRAY 256 #ifdef HAVE_UNISTD_H #include /* for pid_t */ #endif #ifdef PERL_EMBED #include #include #endif #ifdef HAVE_REGEX_H #include /* for regex_t */ #endif typedef struct MYREGEX { #ifdef HAVE_REGEX_H regex_t *ptrs; #endif char *header; char *string; struct MYREGEX *next; /* void *next; */ unsigned char skiparray[SIZEOF_SKIPARRAY]; int case_sensitive; } my_regex, *pmy_regex; /* this is a structure so can add other kill options later */ typedef struct { int hilines; /* nr of lines max article length */ int lowlines; /* nr of lines min article length */ int maxgrps; /* max nr of grps (to prevent spams) */ int maxxref; /* max nr of Xrefs (another spamer) */ unsigned long bodybig; /* max size of article body */ unsigned long bodysmall; /* minimum size of article body */ char quote; /* character to use as quote (for case compare) */ char non_regex; /* character to use as non_regex (don't do regex compare ) */ #ifdef HAVE_REGEX_H int use_extended; /* do we use extended regex */ #endif pmy_regex header; /* scan the entire header */ pmy_regex body; /* scan the body */ pmy_regex list; /* linked list of headers and what to match */ } OneKill, *POneKill; typedef struct { OneKill match; int delkeep; char *group; /* dynamically allocated */ } Group, *PGroup; typedef struct { int Stdin; int Stdout; pid_t Pid; } Child; typedef struct killstruct { FILE *logfp; int logyn; int grp_override; int tie_delete; int totgrps; int ignore_postfix; int xover_log_long; PGroup grps; /* dynamicly allocated array */ int ( *killfunc)(PMaster, struct killstruct *, char *, int); /*function to call to check header */ char *pbody; unsigned long bodylen; int use_extended_regex; Child child; /* these two are last since can't initialize */ OneKill master; #ifdef PERL_EMBED PerlInterpreter *perl_int; #endif } KillStruct, *PKillStruct; /* function prototypes for killfile.c and xover.c*/ int get_one_article_kill(PMaster, int, long); PKillStruct parse_killfile(int, int, int, int); void free_killfile(PKillStruct); int chk_msg_kill(PMaster, PKillStruct, char *, int); int regex_block(char *, pmy_regex, int); /* function prototypes for killprg.c */ int killprg_forkit(PKillStruct, char *, int, int); int chk_msg_kill_fork(PMaster, PKillStruct, char *, int); void killprg_closeit(PKillStruct); void killprg_sendoverview(PMaster); int killprg_sendxover(PMaster, char *); #ifdef PERL_EMBED int killperl_setup(PKillStruct, char *, int, int); int chk_msg_kill_perl(PMaster, PKillStruct, char *, int); void killperl_done(PKillStruct); int killperl_sendxover(PMaster, char *); #endif enum { KILL_XOVER, KILL_KILLFILE}; /* are we doing xover or killfile */ enum { KILL_LOG_NONE, KILL_LOG_SHORT, KILL_LOG_LONG }; /* what level of logging do we use */ enum { DELKEEP_KEEP, DELKEEP_DELETE }; /* is this a keep or delete group */ #define COMMA ',' /* group separator in newsgroup line in article */ #define SPACE ' ' /* another group separator in newsgroup/xref line in article */ #define COLON ':' /* group/article nr separator in Xref line */ #endif /* _SUCK_KILLFILE_H */ suck-4.3.4/killprg.c000066400000000000000000000427651333033562000143130ustar00rootroot00000000000000#include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_DIRENT_H # include #else # define dirent direct # ifdef HAVE_SYS_NDIR_H # include # endif # ifdef HAVE_SYS_DIR_H # include # endif #endif #ifdef HAVE_SYS_WAIT_H # include #endif #ifndef WEXITSTATUS # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >>8) #endif #ifndef WIFEXITED # define WIFEXITED(stat_val) (((stat_val) & 255) == 0) #endif #ifdef HAVE_LIMITS_H #include #endif #include #include #include #ifdef DMALLOC #include #endif #include "suck_config.h" #include "both.h" #include "suck.h" #include "suckutils.h" #include "killfile.h" #include "phrases.h" #ifdef MYSIGNAL #include #endif #ifdef PERL_EMBED #include #include #ifdef OLD_PERL #ifndef ERRSV # define ERRSV (GvSV(errgv)) /* needed for perl 5.004 and earlier */ #endif #ifndef PL_na # define PL_na (na) #endif #endif /* OLD_PERL */ #endif /* KILLPRG.C -------------------------------------------------------------*/ /* this file contains all subroutines pertaining to forking a killfile */ /* program, calling the program for each message and checking the results.*/ /* It also must handle gracefully the child dieing, etc. */ /*------------------------------------------------------------------------*/ /* function prototypes */ int find_in_path(char *); #ifdef MYSIGNAL RETSIGTYPE pipe_sighandler(int); #endif int killprg_forkit(PKillStruct mkill, char *args, int debug, int which) { /*------------------------------------------------------------------------ */ /* take the char string args, parse it to get the program to check to see */ /* if it exists, then try to fork, execl, and set up pipe to it. If */ /* successful, return TRUE, else return FALSE. */ /*-------------------------------------------------------------------------*/ int i, newstdin[2], newstdout[2], retval = FALSE; char *prg[KILLPRG_MAXARGS+1], *myargs = NULL, *ptr, *nptr; /* first parse args into array */ i = strlen(args); /* do this so can muck around with string with messing up original */ if((myargs = malloc(i+1)) == NULL) { error_log(ERRLOG_REPORT, killp_phrases[0], NULL); } else { strcpy(myargs, args); /* strip off nl */ if(myargs[i-1] == '\n') { myargs[i-1] = '\0'; } /* build array of pointers to string, by finding spaces and */ /* replacing them with \0 */ i = 0; ptr = myargs; do { prg[i++] = ptr; nptr = strchr(ptr, ' '); if(nptr != NULL) { *nptr = '\0'; nptr++; } ptr = nptr; } while(ptr != NULL && i < KILLPRG_MAXARGS); prg[i] = NULL; /* must do this for execv */ } if(debug == TRUE) { do_debug("Prg = %s, %d args\n", prg[0], i); } if(prg[0] != NULL && find_in_path(prg[0]) == TRUE ) { /* okay it exists, fork, execl, etc */ /* first set up our pipes */ if(pipe(newstdin) == 0) { if(pipe(newstdout) == 0) { retval = TRUE; } else { close(newstdin[0]); close(newstdin[1]); } } if(retval == FALSE) { MyPerror(killp_phrases[1]); } else { #ifdef MYSIGNAL signal(SIGPIPE, pipe_sighandler); /* set up signal handler if pipe breaks */ signal_block(MYSIGNAL_ADDPIPE); /* set up sgetline() to block signal */ #endif mkill->child.Pid=fork(); if(mkill->child.Pid == 0) { /* in child */ close(newstdin[1]); /* will only be reading from this */ close(newstdout[0]); /* will only be writing to this */ close(0); close(1); /* just to be on safe side */ dup2(newstdin[0], 0); /* copy new read to stdin */ dup2(newstdout[1], 1); /* copy new write to stdout */ execvp(prg[0], prg); exit(-1); /* so if exec fails, we don't get two sucks running */ } else if( mkill->child.Pid == -1) { /* whoops */ MyPerror(killp_phrases[3]); retval = FALSE; /* close down all the pipes */ close(newstdin[0]); close(newstdin[1]); close(newstdout[0]); close(newstdout[1]); } else { /* parent */ close(newstdin[0]); /* will only be writing to new stdin */ close(newstdout[1]); /* will only be reading from new stdout */ /* so subroutine can read/write to child */ mkill->child.Stdin = newstdin[1]; mkill->child.Stdout = newstdout[0]; if ( which == KILL_KILLFILE) { /* we don't do this if it's an xover file, that's done otherwhere */ mkill->killfunc = chk_msg_kill_fork; /* point to our subroutine */ } } } } if(myargs != NULL) { free(myargs); } return retval; } /*-----------------------------------------------------------------------*/ int find_in_path(char *prg) { /* parse the path and search thru it for the program to see if it exists */ int retval = FALSE, len; char fullpath[PATH_MAX+1], *ptr, *mypath, *nptr; struct stat buf; /* if prg has a slant bar in it, its an absolute/relative path, no search done */ if(strchr(prg, '/') == NULL) { ptr = getenv("PATH"); if(ptr != NULL) { len = strlen(ptr)+1; /* now have to copy the environment, since I can't touch the ptr */ if((mypath = malloc(len)) == NULL) { error_log(ERRLOG_REPORT, "%s %s", killp_phrases[2], NULL); } else { strcpy(mypath, ptr); ptr = mypath; /* start us off */ do { nptr = strchr(ptr, PATH_SEPARATOR); if(nptr != NULL) { *nptr = '\0'; /* null terminate the current one */ nptr++; /* move past it */ } /* build the fullpath name */ strcpy(fullpath, ptr); if(ptr[strlen(ptr)-1] != '/') { strcat(fullpath, "/"); } strcat(fullpath, prg); /* okay now have path, check to see if it exists */ if(stat(fullpath, &buf) == 0) { if(S_ISREG(buf.st_mode)) { retval = TRUE; } } /* go onto next one */ ptr = nptr; } while(ptr != NULL && retval == FALSE); free(mypath); } } } /* last ditch try, in case of a relative path or current directory */ if (retval == FALSE) { if(stat(full_path(FP_GET_NOPOSTFIX, FP_NONE, prg), &buf) == 0) { if(S_ISREG(buf.st_mode)) { retval = TRUE; } } } if(retval == FALSE) { error_log(ERRLOG_REPORT, killp_phrases[3], prg, NULL); } return retval; } /*----------------------------------------------------------------------------*/ int chk_msg_kill_fork(PMaster master, PKillStruct pkill, char *header, int headerlen) { /* write the header to ChildStdin, read from ChildStdout */ /* read 0 if get whole message, 1 if skip */ /* return TRUE if kill article, FALSE if keep */ int retval = FALSE, status; char keepyn, keep[4], len[KILLPRG_LENGTHLEN+1]; pid_t waitstatus; /* first make sure our child is alive WNOHANG so if its alive, immed return */ keepyn = -1; /* our error status */ waitstatus = waitpid(pkill->child.Pid, &status, WNOHANG); if(waitstatus == 0) { /* child alive */ if(master->debug == TRUE) { do_debug("Writing to child\n"); } /* first write the length down */ sprintf(len, "%-*d\n", KILLPRG_LENGTHLEN-1, headerlen); if(write(pkill->child.Stdin, len, KILLPRG_LENGTHLEN) <= 0) { error_log(ERRLOG_REPORT, killp_phrases[4], NULL); } /* then write the actual header */ else if(write(pkill->child.Stdin, header, headerlen) <= 0) { error_log(ERRLOG_REPORT, killp_phrases[4], NULL); } /* read the result back */ else { if(master->debug == TRUE) { do_debug("Reading from child\n"); } if(read(pkill->child.Stdout, &keep, 2) <= 0) { error_log(ERRLOG_REPORT, killp_phrases[5], NULL); } else { if(master->debug == TRUE) { keep[2] = '\0'; /* just in case */ do_debug("killprg: read '%s'\n", keep); } keepyn = keep[0] - '0'; /* convert ascii to 0/1 */ } } } else if(waitstatus == -1) { /* huh? */ MyPerror( killp_phrases[6]); } else { /* child died */ error_log(ERRLOG_REPORT, killp_phrases[7], NULL); } switch (keepyn) { case 0: retval = FALSE; break; case 1: retval = TRUE; if(pkill->logyn != KILL_LOG_NONE) { /* log it */ if(pkill->logfp == NULL) { if((pkill->logfp = fopen(full_path(FP_GET, FP_TMPDIR, master->kill_log_name), "a")) == NULL) { MyPerror(killp_phrases[12]); } } if(pkill->logfp != NULL) { /* first print our one line reason */ print_phrases(pkill->logfp, killp_phrases[13], (master->curr)->msgnr, NULL); if(pkill->logyn == KILL_LOG_LONG) { /* print the header as well */ print_phrases(pkill->logfp, "%v1%", header, NULL); } } } if(master->debug == TRUE) { do_debug("Kill program killed: %s", header); } break; default: /* error in child don't use anymore */ retval = FALSE; pkill->killfunc = chk_msg_kill; /* back to normal method */ } return retval; } /*----------------------------------------------------------------------*/ void killprg_closeit(PKillStruct master) { /* tell it to close down by writing a length of zero */ /* then wait for the pid to close down*/ char len[KILLPRG_LENGTHLEN+1]; sprintf(len, "%-*d\n", KILLPRG_LENGTHLEN-1, 0); write(master->child.Stdin, len, KILLPRG_LENGTHLEN); waitpid(master->child.Pid, NULL, 0); } /*---------------------------------------------------------------------*/ #ifdef MYSIGNAL RETSIGTYPE pipe_sighandler(int what) { /* we don't have to do anything else, since the routine above will detect a dead child */ /* and handle it appropriately*/ int status; error_log(ERRLOG_REPORT, killp_phrases[8], NULL); wait(&status); if(WIFEXITED(status) != 0) { error_log(ERRLOG_REPORT, killp_phrases[9], str_int(WEXITSTATUS(status)), NULL); } else if(WIFSIGNALED(status) != 0) { error_log(ERRLOG_REPORT, killp_phrases[10], str_int(WTERMSIG(status)), NULL); } else { error_log(ERRLOG_REPORT, killp_phrases[11], NULL); } } #endif #ifdef PERL_EMBED /*---------------------------------------------------------------------*/ /* MUCH OF THIS CODE COMES From the PERLEMBED man pages that comes */ /* with perl */ /*---------------------------------------------------------------------*/ int killperl_setup(PKillStruct master, char *prg, int debug, int which) { int retval = TRUE; char *ptr = NULL; char *prgs[] = { ptr, ptr}; if((master->perl_int = perl_alloc()) == NULL) { retval = FALSE; error_log(ERRLOG_REPORT, killp_phrases[14], NULL); } else { if(debug == TRUE) { do_debug("Perl program name = %s\n", prg); } perl_construct(master->perl_int); prgs[1] = prg; if(perl_parse(master->perl_int, NULL, 2, prgs, NULL) == 0) { perl_run(master->perl_int); if(which == KILL_KILLFILE) { /* we don't do this for xover, it's handled separately */ master->killfunc = chk_msg_kill_perl; } } else { error_log(ERRLOG_REPORT, killp_phrases[15], prg, NULL); killperl_done(master); } } return retval; } /*--------------------------------------------------------------------------*/ void killperl_done(PKillStruct master) { perl_destruct(master->perl_int); perl_free(master->perl_int); master->perl_int = NULL; master->killfunc=chk_msg_kill; /* restore default function */ } /*----------------------------------------------------------------------------*/ int chk_msg_kill_perl(PMaster master, PKillStruct killp, char *hdr, int hdrlen) { /* return TRUE to kill article, FALSE to keep */ /* this code comes from the both perlembed and perlcall man pages */ int i, retval = FALSE; char *args[2] = { NULL, NULL}; dSP; /* Perl stack pointer */ /* first set up args */ args[0] = hdr; if(master->debug == TRUE) { do_debug("Calling %s\n", PERL_PACKAGE_SUB); } ENTER; SAVETMPS; PUSHMARK(SP); i = perl_call_argv(PERL_PACKAGE_SUB, G_SCALAR | G_EVAL | G_KEEPERR, args); SPAGAIN; if(SvTRUE(ERRSV)) { error_log(ERRLOG_REPORT, killp_phrases[16], SvPV(ERRSV,PL_na), NULL); POPs; killperl_done(killp); } else if(i != 1) { error_log(ERRLOG_REPORT, killp_phrases[17], PERL_PACKAGE_SUB, NULL); killperl_done(killp); } else { /* 0 = keep 1 = delete */ i = POPi; if(master->debug == TRUE) { do_debug("retval = %d\n", i); } retval = (i == 0) ? FALSE : TRUE; } PUTBACK; FREETMPS; LEAVE; if(retval == TRUE && killp->logyn != KILL_LOG_NONE) { /* log it */ if(killp->logfp == NULL) { if((killp->logfp = fopen(full_path(FP_GET, FP_TMPDIR, master->kill_log_name), "a")) == NULL) { MyPerror(killp_phrases[19]); } } if(killp->logfp != NULL) { /* first print our one line reason */ print_phrases(killp->logfp, killp_phrases[18], (master->curr)->msgnr, NULL); if(killp->logyn == KILL_LOG_LONG) { /* print the header as well */ print_phrases(killp->logfp, "%v1%", hdr, NULL); } } } return retval; } #endif /*---------------------------------------------------------------------------------*/ void killprg_sendoverview(PMaster master) { PKillStruct killp; POverview overp; char buf[MAXLINLEN], len[KILLPRG_LENGTHLEN+1]; int count, status; pid_t waitstatus; #ifdef PERL_EMBED char *args[2] = { NULL, NULL}; #endif killp = master->xoverp; overp = master->xoverview; /* send the overview.fmt to the child process, if it exists */ if(killp != NULL && overp != NULL) { /* first, rebuild the overview.fmt */ count = 0; buf[0] = '\0'; while(overp != NULL) { if(count > 0) { strcat(buf, "\t"); /* add the separator */ } strcat(buf, overp->header); if(overp->full == TRUE) { strcat(buf, "full"); } overp = overp->next; count++; } strcat(buf, "\n"); count = strlen(buf); if(killp->child.Pid != -1) { waitstatus = waitpid(killp->child.Pid, &status, WNOHANG); if(waitstatus == 0) { if(master->debug == TRUE) { do_debug("sending overview.fmt, len = %d - %s", count, buf); } /* first write the length down */ sprintf(len, "%-*d\n", KILLPRG_LENGTHLEN-1, count); if(write(killp->child.Stdin, len, KILLPRG_LENGTHLEN) <= 0) { error_log(ERRLOG_REPORT, killp_phrases[4], NULL); } /* then write the actual header */ else if(write(killp->child.Stdin, buf, count) <= 0) { error_log(ERRLOG_REPORT, killp_phrases[4], NULL); } } } #ifdef PERL_EMBED /* this code comes from the both perlembed and perlcall man pages */ else if(killp->perl_int != NULL) { dSP; /* Perl stack pointer */ /* first set up args */ args[0] = buf; if(master->debug == TRUE) { do_debug("Calling %s\n", PERL_OVER_SUB); } ENTER; SAVETMPS; PUSHMARK(SP); status = perl_call_argv(PERL_OVER_SUB, G_VOID | G_EVAL | G_KEEPERR, args); SPAGAIN; if(SvTRUE(ERRSV)) { error_log(ERRLOG_REPORT, killp_phrases[16], SvPV(ERRSV,PL_na), NULL); POPs; killperl_done(killp); } else if(status != 1) { if(master->debug == TRUE) { do_debug("return from perl_call_argv = %d\n", status); } error_log(ERRLOG_REPORT, killp_phrases[17], PERL_OVER_SUB, NULL); killperl_done(killp); } PUTBACK; FREETMPS; LEAVE; } #endif } } /*----------------------------------------------------------------------------------*/ int killprg_sendxover(PMaster master, char *linein) { /* this is almost a carbon copy of chk_msg_kill_fork() */ int retval = FALSE, status, headerlen; char keepyn, keep[4], len[KILLPRG_LENGTHLEN+1]; pid_t waitstatus; PKillStruct pkill; headerlen = strlen(linein); pkill = master->xoverp; /* first make sure our child is alive WNOHANG so if its alive, immed return */ keepyn = -1; /* our error status */ waitstatus = waitpid(pkill->child.Pid, &status, WNOHANG); if(waitstatus == 0) { /* child alive */ if(master->debug == TRUE) { do_debug("Writing to child\n"); } /* first write the length down */ sprintf(len, "%-*d\n", KILLPRG_LENGTHLEN-1, headerlen); if(write(pkill->child.Stdin, len, KILLPRG_LENGTHLEN) <= 0) { error_log(ERRLOG_REPORT, killp_phrases[4], NULL); } /* then write the actual header */ else if(write(pkill->child.Stdin, linein, headerlen) <= 0) { error_log(ERRLOG_REPORT, killp_phrases[4], NULL); } /* read the result back */ else { if(master->debug == TRUE) { do_debug("Reading from child\n"); } if(read(pkill->child.Stdout, &keep, 2) <= 0) { error_log(ERRLOG_REPORT, killp_phrases[5], NULL); } else { if(master->debug == TRUE) { keep[2] = '\0'; /* just in case */ do_debug("killprg: read '%s'\n", keep); } keepyn = keep[0] - '0'; /* convert ascii to 0/1 */ } } } else if(waitstatus == -1) { /* huh? */ MyPerror( killp_phrases[6]); } else { /* child died */ error_log(ERRLOG_REPORT, killp_phrases[7], NULL); } retval = ( keepyn == 1) ? TRUE : FALSE; return retval; } /*-----------------------------------------------------------------------------------*/ #ifdef PERL_EMBED int killperl_sendxover(PMaster master, char *linein) { /* this routine is almost a carbon copy of chk_msg_kill_perl() */ int i, retval = FALSE; char *args[2] = { NULL, NULL}; dSP; /* Perl stack pointer */ /* first set up args */ args[0] = linein; if(master->debug == TRUE) { do_debug("Calling %s\n", PERL_XOVER_SUB); } ENTER; SAVETMPS; PUSHMARK(SP); i = perl_call_argv(PERL_XOVER_SUB, G_SCALAR | G_EVAL | G_KEEPERR, args); SPAGAIN; if(SvTRUE(ERRSV)) { error_log(ERRLOG_REPORT, killp_phrases[16], SvPV(ERRSV,PL_na), NULL); POPs; killperl_done(master->xoverp); } else if(i != 1) { error_log(ERRLOG_REPORT, killp_phrases[17], PERL_XOVER_SUB, NULL); killperl_done(master->xoverp); } else { /* 0 = keep 1 = delete */ i = POPi; if(master->debug == TRUE) { do_debug("retval = %d\n", i); } retval = (i == 0) ? FALSE : TRUE; } PUTBACK; FREETMPS; LEAVE; return retval; } #endif suck-4.3.4/lmove-config000066400000000000000000000001041333033562000147700ustar00rootroot00000000000000BASE=/home/boby/suck-4.2.2/test ACTIVE=/home/boby/suck-4.2.2/active suck-4.3.4/lmove.c000066400000000000000000000532341333033562000137620ustar00rootroot00000000000000#include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_DIRENT_H # include #else # define dirent direct # ifdef HAVE_SYS_NDIR_H # include # endif # ifdef HAVE_SYS_DIR_H # include # endif #endif #ifdef HAVE_LIMITS_H #include #endif #include #include #include #include #include #ifdef DMALLOC #include #endif #include "suck_config.h" #include "both.h" #include "phrases.h" /*----------------------------------------------------*/ typedef struct { char *group; unsigned int lownr; unsigned int highnr; char active; } Group, *PGroup; /* Master Structure */ typedef struct { FILE *msgs; int nrgroups; int debug; int abort_exist; /* do we abort if article nr already exists */ int link_type; PGroup groups; char active_file[PATH_MAX+1]; const char *config_file; char basedir[PATH_MAX+1]; const char *msgdir; const char *phrases; } Master, *PMaster; enum { RETVAL_ERROR = -1, RETVAL_OK = 0 }; enum { LOCKFILE_LOCK, LOCKFILE_UNLOCK }; enum { LINK_SYM, LINK_HARD, LINK_NONE }; #define NEWSGROUP "Newsgroups: " #define NEWSGROUP_LEN 12 #define GROUP_SEP ',' #define CONFIG_BASE "BASE=" #define CONFIG_ACTIVE "ACTIVE=" #define CONFIG_BASE_LEN 5 #define CONFIG_ACTIVE_LEN 7 /* for phrases stuff */ char **both_phrases = default_both_phrases; char **lmove_phrases = default_lmove_phrases; /*-------------------------------------------------------*/ /* function prototypes */ void scan_args(PMaster, int, char *[]); void load_phrases(PMaster); void free_phrases(void); int load_active(PMaster); int load_config(PMaster); void free_active(PMaster); int check_dirs(PMaster); char *make_dirname(char *, char *); int move_msgs(PMaster); char *find_groups(PMaster, char *); PGroup match_group(PMaster, char **); int rewrite_active(PMaster); int do_lockfile(PMaster, int); /*--------------------------------------------------------------------------------*/ int main(int argc, char *argv[]) { int retval = RETVAL_OK; const char *ptr; Master master; /* set up defaults */ master.msgs = stdout; master.nrgroups = 0; master.debug = 0; master.groups = NULL; strcpy(master.active_file,N_LMOVE_ACTIVE); master.config_file=N_LMOVE_CONFIG; master.phrases = NULL; master.basedir[0] = '\0'; master.msgdir = NULL; master.abort_exist = TRUE; master.link_type = LINK_NONE; scan_args(&master, argc, argv); load_phrases(&master); /* this has to be here so rest prints okay */ /* have to load this first so get basedir */ retval = load_config(&master); if(master.debug == TRUE) { do_debug("master.active_file = %s\n", (master.active_file == NULL) ? "NULL" : master.active_file); do_debug("master.config_file = %s\n", (master.config_file == NULL) ? "NULL" : master.config_file); do_debug("master.basedir = %s\n", master.basedir); do_debug("master.msgdir = %s\n", (master.msgdir == NULL) ? "NULL" : master.msgdir); do_debug("master.phrases = %s\n", (master.phrases == NULL) ? "NULL" :master.phrases); ptr = "WHOOPS Wierd value for link_type"; switch (master.link_type) { case LINK_SYM: ptr = "SYMBOLIC"; break; case LINK_HARD: ptr = "HARD"; break; case LINK_NONE: ptr = "NO LINKS"; break; } do_debug("master.link_type = %s\n", ptr); } if(master.basedir == NULL || master.basedir[0] == '\0') { error_log(ERRLOG_REPORT, lmove_phrases[5], NULL); retval = RETVAL_ERROR; } if(master.msgdir == NULL || master.msgdir[0] == '\0') { error_log(ERRLOG_REPORT, lmove_phrases[7], NULL); retval = RETVAL_ERROR; } if(retval == RETVAL_OK) { #ifdef LOCKFILE if(do_lockfile(&master, LOCKFILE_LOCK) != RETVAL_OK) { retval = RETVAL_ERROR; } else { #endif if((retval = load_active(&master)) == RETVAL_OK) { /* check to make sure all our directories are there */ if((retval = check_dirs(&master)) == RETVAL_OK) { retval = move_msgs(&master); if(retval == RETVAL_OK) { retval = rewrite_active(&master); } } } free_active(&master); #ifdef LOCKFILE do_lockfile(&master, LOCKFILE_UNLOCK); } #endif } free_phrases(); exit(retval); } /*-----------------------------------------------------------------------------------*/ int rewrite_active(PMaster master) { /* move old active to active.old and write active */ FILE *fpo; char fname[PATH_MAX+1]; int i, retval = RETVAL_OK; sprintf(fname, "%s.old", master->active_file); if(rename(master->active_file, fname) != 0) { MyPerror(fname); retval = RETVAL_ERROR; } else if((fpo = fopen(master->active_file, "w")) == NULL) { MyPerror(master->active_file); retval = RETVAL_ERROR; } else { for(i = 0; i < master->nrgroups && retval == RETVAL_OK ; i++) { if(fprintf(fpo, "%s %u %u %c\n", master->groups[i].group, master->groups[i].highnr, master->groups[i].lownr, master->groups[i].active) <= 0) { error_log(ERRLOG_REPORT, lmove_phrases[13], master->active_file, NULL); retval = RETVAL_ERROR; } } fclose(fpo); if(chmod(master->active_file, LMOVE_ACTIVE_MODE) != 0) { MyPerror(master->active_file); } } return retval; } /*-----------------------------------------------------------------------------------*/ int move_msgs(PMaster master) { /* go thru msg dir, and move the messages, based on "Newsgroups: subj line" */ /* into newsgroup directory */ struct dirent *entry; DIR *dptr; struct stat statbuf; char *ptr; PGroup group; char o_fname[PATH_MAX+1], n_fname[PATH_MAX+1]; struct stat sbuf; int quit = FALSE, retval = RETVAL_OK, count; int (*linkit)(const char *, const char *); /* which type of link will we create, symbolic or hard? Point to the right function */ /* this won't get used if link_type is NONE, so we don't handle that case */ #ifdef EMX /* OS-2 doesn't have link or symlink */ linkit = NULL; master->link_type = LINK_NONE; #else linkit = (master->link_type == LINK_SYM) ? symlink : link; #endif if((dptr = opendir(master->msgdir)) == NULL) { MyPerror(master->msgdir); retval = RETVAL_ERROR; } else { while(((entry = readdir(dptr)) != NULL) && quit == FALSE) { sprintf(o_fname, "%s/%s", master->msgdir, entry->d_name); /* build full path */ if(stat(o_fname, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) { if((ptr = find_groups(master, o_fname)) == NULL) { error_log(ERRLOG_REPORT, lmove_phrases[11], entry->d_name, NULL); } else { count = 0; /* how many groups have we matched */ ptr += NEWSGROUP_LEN; /* skip header */ /* we pass a pointer to a pointer so that the subroutine can modify it */ /* and return where we left off, so we can do multiple groups on one line */ while(ptr != NULL && *ptr != '\0' && (group = match_group(master, &ptr)) != NULL) { count++; sprintf(n_fname, "%s/%d", make_dirname(master->basedir, group->group), group->highnr); if(stat(n_fname, &sbuf) == 0) { /* file exists */ if(master->abort_exist == TRUE) { error_log(ERRLOG_REPORT, lmove_phrases[21], group->group, str_int(group->highnr), NULL); quit = TRUE; } else { while(stat(n_fname, &sbuf) == 0) { /* up it and try again */ group->highnr++; sprintf(n_fname, "%s/%d", make_dirname(master->basedir, group->group), group->highnr); } } } if(quit != TRUE) { if(count == 1) { /* first group gets the file */ if(rename(o_fname, n_fname) != 0) { error_log(ERRLOG_REPORT, lmove_phrases[13], o_fname, n_fname, NULL); MyPerror(""); } else { if(master->debug == TRUE) { do_debug("Moved %s to %s\n", o_fname, n_fname, NULL); } group->highnr++; strcpy(o_fname, n_fname); /* so we can link to this file */ } } else if (master->link_type == LINK_NONE) { /* this will break the loop */ ptr = NULL; } else { /* remainder of groups get links to first file */ /* call the function reffed earlier */ if(master->debug == TRUE) { do_debug("linking %s to %s\n", n_fname, o_fname, NULL); } if((*linkit)(o_fname, n_fname) != 0) { MyPerror(n_fname); } else { group->highnr++; } } } } if(count == 0) { error_log(ERRLOG_REPORT, lmove_phrases[12], entry->d_name, NULL); } } } } closedir(dptr); } return retval; } /*-----------------------------------------------------------------------------------*/ PGroup match_group(PMaster master, char **newsgroups) { PGroup retval = NULL; int i; char *ptr, *eptr, save; /* this gets a null-terminated line of newsgroups */ ptr = eptr = *newsgroups; /* this is a pointer to a pointer, so can do multiple groups per line */ /* in case the newsgroup line is formatted badly, and has extra spaces in the front */ while (isspace(*ptr)) { ptr++; eptr++; } do { /* first find end of this group or NULL */ /* the isspace handles the case where they use spaces either between */ /* groups, or at the end of the line. */ while(*eptr != GROUP_SEP && *eptr != '\0' && !isspace(*eptr) && *eptr) { eptr++; } save = *eptr; *eptr = '\0'; /* so can use ptr as group name */ for(i = 0 ; retval == NULL && i < master->nrgroups;i++) { if(strcmp(master->groups[i].group, ptr) == 0) { /* bingo */ if(master->debug == TRUE) { do_debug("Matched group %s\n", ptr); } retval = &(master->groups[i]); } } ptr = ++eptr; /* set up for next group */ } while( save != '\0' && retval == NULL); /* the eptr -1 is because the ++eptr above puts us 1 beyond the EOL */ if(retval == NULL) { /* if no matches , return EOL */ *newsgroups = eptr -1; } else { /* if at end of line, return that, else return start of next group */ *newsgroups = (save == '\0') ? eptr-1 : eptr; } return retval; } /*-----------------------------------------------------------------------------------*/ char *find_groups(PMaster master, char *fname) { FILE *fpi; char *retval; static char linein[LMOVE_MAXLINLEN+1]; int i; retval = NULL; if((fpi = fopen(fname, "r")) == NULL) { MyPerror(fname); } else { while(retval == NULL && fgets(linein, LMOVE_MAXLINLEN, fpi) != NULL) { if(strncmp(linein, NEWSGROUP, NEWSGROUP_LEN) == 0) { retval = linein; /* now strip off NL off of EOL */ i = strlen(linein) - 1; if(linein[i] == '\n') { linein[i] = '\0'; } if(master->debug == TRUE) { do_debug("Found newsgroup line--%s--\n",linein, NULL); } } } fclose(fpi); } return retval; } /*-----------------------------------------------------------------------------------*/ int load_config(PMaster master) { int i, retval = RETVAL_OK; FILE *fpi; char linein[MAXLINLEN+1]; if((fpi = fopen(master->config_file, "r")) == NULL) { MyPerror(master->config_file); retval = RETVAL_ERROR; } else { while( fgets(linein, MAXLINLEN, fpi) != NULL) { /* strip the nl */ i = strlen(linein); if(linein[i-1] == '\n') { linein[i-1] = '\0'; } if(strncmp(linein, CONFIG_BASE, CONFIG_BASE_LEN) == 0) { strcpy(master->basedir, &linein[CONFIG_BASE_LEN]); } else if(strncmp(linein, CONFIG_ACTIVE, CONFIG_ACTIVE_LEN) == 0) { strcpy(master->active_file, &linein[CONFIG_ACTIVE_LEN]); } else { error_log(ERRLOG_REPORT, lmove_phrases[20], NULL); } } fclose(fpi); } return retval; } /*-----------------------------------------------------------------------------------*/ int load_active(PMaster master) { int i, j, retval = RETVAL_OK; unsigned int lownr, highnr; FILE *fpi; char linein[MAXLINLEN+1], group[MAXLINLEN+1], active_char; /* each line in active is in the form group name high_msg_nr low_msg_nr active_char */ if((fpi = fopen(master->active_file, "r")) == NULL) { MyPerror(master->active_file); retval = RETVAL_ERROR; } else { /* pass one, count the number of valid entries */ /* and get the BASE dir and MSG dir entries */ while((i = fscanf(fpi, "%s %u %u %c", linein,&highnr,&lownr,&active_char)) != EOF) { if(i == 4) { master->nrgroups++; } else { error_log(ERRLOG_REPORT, lmove_phrases[9], linein, NULL); } } if(master->debug == TRUE) { do_debug("Nr of groups in active = %d\n", master->nrgroups); } fseek(fpi, 0L, SEEK_SET); /* rewind for pass two */ /* pass two, put them into the master->groups array */ if((master->groups = calloc(master->nrgroups, sizeof(Group))) == NULL) { error_log(ERRLOG_REPORT, lmove_phrases[8], NULL); retval = RETVAL_ERROR; } else { i = 0; while(retval == RETVAL_OK && fgets(linein, MAXLINLEN, fpi) != NULL) { if(sscanf(linein, "%s %u %u %c", group, &highnr, &lownr, &active_char) == 4) { if(highnr == 0) { highnr = 1; } /* so we start at 1 */ master->groups[i].lownr = lownr; master->groups[i].highnr = highnr; master->groups[i].active = active_char; /* now check for dupe groups */ for(j = 0 ; j < i; j++) { if(strcmp(master->groups[j].group, group) == 0) { error_log(ERRLOG_REPORT, lmove_phrases[22], group, NULL); retval = RETVAL_ERROR; break; /* so we don't check any further */ } } if((retval == RETVAL_OK) && ((master->groups[i].group = malloc(strlen(group)+1)) == NULL)) { error_log(ERRLOG_REPORT, lmove_phrases[8], NULL); retval = RETVAL_ERROR; } else { if(master->debug == TRUE) { do_debug("Group %d = %s\n", i, group); } strcpy(master->groups[i].group, group); i++; } } } } fclose(fpi); } return retval; } /*------------------------------------------------------------------------------------*/ void free_active(PMaster master) { int i; for(i=0;inrgroups;i++) { if(master->groups[i].group != NULL) { free(master->groups[i].group); } } free(master->groups); master->groups = NULL; } /*------------------------------------------------------------------------------------*/ int check_dirs(PMaster master) { /* check to make sure the base, msg, and all group dirs are available */ struct stat statbuf; int i, y, retval = RETVAL_OK; char *ptr, path[PATH_MAX+1]; if(stat(master->basedir, &statbuf) != 0 || ! S_ISDIR(statbuf.st_mode)) { error_log(ERRLOG_REPORT, lmove_phrases[10], master->basedir); retval = RETVAL_ERROR; } else if(stat(master->msgdir, &statbuf) != 0 || ! S_ISDIR(statbuf.st_mode)) { error_log(ERRLOG_REPORT, lmove_phrases[10], master->msgdir, NULL); retval = RETVAL_ERROR; } else { for(i = 0 ; i < master->nrgroups && retval == RETVAL_OK ; i++) { ptr = make_dirname(master->basedir, master->groups[i].group); if(master->debug == TRUE) { do_debug("Checking dir %s\n", ptr); } if(stat(ptr, &statbuf) != 0 && errno == ENOENT) { /* it don't exist. try to make it*/ /* lets work our way down and try to make all dirs */ sprintf(path, "%s/", master->basedir); y = strlen(path); /* start at end of string */ ptr = master->groups[i].group; while(retval == RETVAL_OK && *ptr != '\0') { /* copy til next dit or end */ while(*ptr != '.' && *ptr != '\0') { path[y] = *ptr; y++; ptr++; } path[y] = '\0'; /* just to be on safe side */ if(master->debug == TRUE) { do_debug("Trying to make dir %s : mode %o\n", path, LMOVE_DEFAULT_DIR_MODE); } umask(0); /* so mode defined is mode we actually get */ if(mkdir(path, LMOVE_DEFAULT_DIR_MODE) != 0 && errno != EEXIST) { MyPerror(path); retval = RETVAL_ERROR; } if(*ptr != '\0') { /* move on to next */ path[y++] = '/'; ptr++; } } } /* one final check */ if(retval == RETVAL_OK) { /* have to redo stat, since we might have made dirs above */ ptr = make_dirname(master->basedir, master->groups[i].group); if(stat(ptr, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) { error_log(ERRLOG_REPORT, lmove_phrases[10], ptr, NULL); retval = RETVAL_ERROR; } } } } return retval; } /*------------------------------------------------------------------------------------*/ void scan_args(PMaster master, int argc, char *argv[]) { int loop; for(loop = 1; loop < argc; loop++) { if(argv[loop][0] != '-' || argv[loop][2] != '\0') { error_log(ERRLOG_REPORT, lmove_phrases[0], argv[loop], NULL); } else { switch(argv[loop][1]) { case 'a': /* active file */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, lmove_phrases[6], NULL); } else { strcpy(master->active_file,argv[++loop]); } break; case 'A': /* don't abort if article nr already exists on disk */ master->abort_exist = FALSE; break; case 'c': /* config file */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, lmove_phrases[19], NULL); } else { master->config_file =argv[++loop]; } break; case 'd': /* article directory */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, lmove_phrases[7], NULL); } else { master->msgdir = argv[++loop]; } break; case 'D': /* debug */ master->debug = TRUE; break; case 'e': /* use default error log path */ error_log(ERRLOG_SET_FILE, ERROR_LOG,NULL); break; case 'E': /* error log path */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, "%s\n",lmove_phrases[1],NULL); } else { error_log(ERRLOG_SET_FILE, argv[++loop], NULL); } break; case 'h': /* use hard vice symbolic links */ master->link_type = LINK_HARD; break; case 'l': /* language phrase file */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, lmove_phrases[4],NULL); } else { master->phrases = argv[++loop]; } break; case 's': /* do symbolic links */ master->link_type = LINK_SYM; break; default: error_log(ERRLOG_REPORT, lmove_phrases[0], argv[loop],NULL); break; } } } } /*--------------------------------------------------------------------------------*/ char *make_dirname(char *base, char *group) { static char path[PATH_MAX+1]; int i; char *ptr; /* take base dir & a group name, change all "." in group to "/" */ /* and return the full path */ i = strlen(base); strcpy(path, base); ptr = &path[i]; sprintf(path, "%s/%s", base, group); while ( *ptr != '\0' ) { if(*ptr == '.') { *ptr = '/'; } ptr++; } return path; } #ifdef LOCKFILE /*--------------------------------------------------------------*/ int do_lockfile(PMaster master, int option) { static char lockfile[PATH_MAX+1]; int retval; FILE *f_lock; pid_t pid; retval = RETVAL_OK; if(option == LOCKFILE_UNLOCK) { unlink(lockfile); } else { sprintf(lockfile, "%s/%s", master->basedir, N_LMOVE_LOCKFILE); if((f_lock = fopen(lockfile, "r")) != NULL) { /* okay, let's try and see if this sucker is truly alive */ long tmp = 0; fscanf(f_lock, "%ld", &tmp); fclose(f_lock); pid = (pid_t)tmp; if(pid <= 0) { error_log(ERRLOG_REPORT, lmove_phrases[14], lockfile, NULL); retval = RETVAL_ERROR; } /* this next technique borrowed from pppd, sys-linux.c (ppp2.2.0c) */ /* if the pid doesn't exist (can't send any signal to it), then try */ /* to remove the stale PID file so can recreate it. */ else if(kill(pid, 0) == -1 && errno == ESRCH) { /* no pid found */ if(unlink(lockfile) == 0) { /* this isn't error so don't put in error log */ print_phrases(master->msgs, lmove_phrases[15], lockfile, NULL); } else { error_log(ERRLOG_REPORT, lmove_phrases[16], lockfile, NULL); retval = RETVAL_ERROR; } } else { error_log(ERRLOG_REPORT, lmove_phrases[17], lockfile, NULL); retval = RETVAL_ERROR; } } if(retval == RETVAL_OK) { if((f_lock = fopen(lockfile, "w")) != NULL) { fprintf(f_lock, "%ld", (long) getpid()); fclose(f_lock); } else { error_log(ERRLOG_REPORT, lmove_phrases[18], lockfile, NULL); retval = RETVAL_ERROR; } } } return retval; } #endif /*--------------------------------------------------------------------------------*/ /* THE strings in this routine is the only one not in the arrays, since */ /* we are in the middle of reading the arrays, and they may or may not be valid. */ /*--------------------------------------------------------------------------------*/ void load_phrases(PMaster master) { int error=TRUE; FILE *fpi; char buf[MAXLINLEN]; if(master->phrases != NULL) { if((fpi = fopen(master->phrases, "r")) == NULL) { MyPerror(master->phrases); } else { fgets(buf, MAXLINLEN, fpi); if(strncmp( buf, SUCK_VERSION, strlen(SUCK_VERSION)) != 0) { error_log(ERRLOG_REPORT, "Invalid Phrase File, wrong version\n", NULL); } else if((both_phrases = read_array(fpi, NR_BOTH_PHRASES, TRUE)) != NULL) { read_array(fpi, NR_RPOST_PHRASES, FALSE); /* skip these */ read_array(fpi, NR_TEST_PHRASES, FALSE); read_array(fpi, NR_SUCK_PHRASES, FALSE); read_array(fpi, NR_TIMER_PHRASES, FALSE); read_array(fpi, NR_CHKH_PHRASES, FALSE); read_array(fpi, NR_DEDUPE_PHRASES, FALSE); read_array(fpi, NR_KILLF_REASONS, FALSE); read_array(fpi, NR_KILLF_PHRASES, FALSE); read_array(fpi, NR_KILLP_PHRASES, FALSE); if((lmove_phrases = read_array(fpi, NR_LMOVE_PHRASES, TRUE)) != NULL) { error = FALSE; } } } fclose(fpi); if(error == TRUE) { /* reset back to default */ error_log(ERRLOG_REPORT, "Using default Language phrases\n",NULL); lmove_phrases = default_lmove_phrases; both_phrases = default_both_phrases; } } } /*--------------------------------------------------------------------------------*/ void free_phrases(void) { /* free up the memory alloced in load_phrases() */ if(lmove_phrases != default_lmove_phrases) { free_array(NR_LMOVE_PHRASES, lmove_phrases); } if(both_phrases != default_both_phrases) { free_array(NR_BOTH_PHRASES, both_phrases); } } suck-4.3.4/lmove_phrases.c000066400000000000000000000022201333033562000154740ustar00rootroot00000000000000/* the default phrases */ const char *default_lmove_phrases[] = { "Invalid argument: %v1%, ignoring\n", "No Error Log name provided\n", "lmove: Status Log", "No Status Log name provided\n", "No language file specified\n", "No Base Directory provided\n", /* 5 */ "No active file name provided, using default.\n", "No Article Directory provided\n", "Out of memory loading active file\n", "Invalid line in active file, ignoring: %v1%", "Error with directory : %v1% : aborting\n", /* 10 */ "No Newsgroups line in file %v1%, ignoring\n", "Couldn't find valid newsgroups for file %v1%, ignoring\n", "Error writing to %v1%, aborting\n", "Lock File %v1%, Invalid PID, aborting\n", "Lock File %v1%, stale PID, removed\n", /* 15 */ "Lock File %v1%, stale PID, Unable to remove, aborting\n", "Lock file %v1%, PID exists, aborting\n", "Unable to create Lock File %v1%\n", "No configuration file name provided\n", "Invalid configuration file format\n", /* 20 */ "Group %v1% Article %v2% exists, aborting\n", "Duplicate Group in active file - %v1%, aborting\n", }; int nr_lmove_phrases = sizeof(default_lmove_phrases)/sizeof(default_lmove_phrases[0]); suck-4.3.4/lpost.c000066400000000000000000000016501333033562000137740ustar00rootroot00000000000000#include #include #include #include #include #include "suck_config.h" /* get news data from stdin and post it to the local server */ int main(int argc,char *argv[]) { FILE *pfp=NULL; const int max_len = 1024; char line[max_len]; int count=0,verbose=0, retval=0; size_t len; if (argc>1) { verbose=1; } while(fgets(line, max_len, stdin) != NULL && retval == 0) { len=strlen(line); if (pfp == NULL) { if (verbose != 0) { printf("posting article %d\n", ++count); } pfp = popen(RNEWS, "w"); } if(pfp == NULL) { perror("Error: cannot open rnews: "); retval = -1; } else if (line[0] == '.' && len == 1) { /* end of article */ if (verbose != 0) { printf("end of article %d\n",count); } if(pfp != NULL) { pclose(pfp); pfp = NULL; } } else { (void) fputs(line, pfp); } } /* end while */ exit(retval); } suck-4.3.4/makephrases.c000066400000000000000000000227231333033562000151420ustar00rootroot00000000000000 #include #include #include #include #include "suck_config.h" /* create a text file with the default*phrases arrays in them */ /* and create phrases.h */ #define PHRASES_H "phrases.h" extern const char *default_both_phrases[]; extern const char *default_rpost_phrases[]; extern const char *default_test_phrases[]; extern const char *default_suck_phrases[]; extern const char *default_timer_phrases[]; extern const char *default_chkh_phrases[]; extern const char *default_dedupe_phrases[]; extern const char *default_killf_reasons[]; extern const char *default_killf_phrases[]; extern const char *default_killp_phrases[]; extern const char *default_sucku_phrases[]; extern const char *default_lmove_phrases[]; extern const char *default_active_phrases[]; extern const char *default_batch_phrases[]; extern const char *default_xover_phrases[]; extern const char *default_xover_reasons[]; extern int nr_both_phrases; extern int nr_rpost_phrases; extern int nr_test_phrases; extern int nr_suck_phrases; extern int nr_timer_phrases; extern int nr_chkh_phrases; extern int nr_dedupe_phrases; extern int nr_killf_reasons; extern int nr_killf_phrases; extern int nr_killp_phrases; extern int nr_sucku_phrases; extern int nr_lmove_phrases; extern int nr_active_phrases; extern int nr_batch_phrases; extern int nr_xover_phrases; extern int nr_xover_reasons; int do_one_array(FILE *, int, const char **); int do_phrases_h(void); int count_vars(const char **, int); int main(int argc, char *argv[]) { int retval = 0; FILE *fpo; if(argc != 2) { fprintf(stderr, "Usage: %s outfile \n", argv[0]); retval = -1; } else if(strcmp(argv[1],"phrases.h") == 0) { retval = do_phrases_h(); } else if((fpo = fopen(argv[1], "w")) == NULL) { perror(argv[1]); retval = -1; } else { /* first put version number */ fprintf(fpo, "%s\n", SUCK_VERSION); retval = do_one_array(fpo, nr_both_phrases, default_both_phrases); if(retval == 0) { retval = do_one_array(fpo, nr_rpost_phrases, default_rpost_phrases); } if(retval == 0) { retval = do_one_array(fpo, nr_test_phrases, default_test_phrases); } if(retval == 0) { retval = do_one_array(fpo, nr_suck_phrases, default_suck_phrases); } if(retval == 0) { retval = do_one_array(fpo, nr_timer_phrases, default_timer_phrases); } if(retval == 0) { retval = do_one_array(fpo, nr_chkh_phrases, default_chkh_phrases); } if(retval == 0) { retval = do_one_array(fpo, nr_dedupe_phrases, default_dedupe_phrases); } if(retval == 0) { retval = do_one_array(fpo, nr_killf_reasons, default_killf_reasons); } if(retval == 0) { retval = do_one_array(fpo, nr_killf_phrases, default_killf_phrases); } if(retval == 0) { retval = do_one_array(fpo, nr_killp_phrases, default_killp_phrases); } if(retval == 0) { retval = do_one_array(fpo, nr_sucku_phrases, default_sucku_phrases); } if(retval == 0) { retval = do_one_array(fpo, nr_lmove_phrases, default_lmove_phrases); } if(retval == 0) { retval = do_one_array(fpo, nr_active_phrases, default_active_phrases); } if(retval == 0) { retval = do_one_array(fpo, nr_batch_phrases, default_batch_phrases); } if(retval == 0) { retval = do_one_array(fpo, nr_xover_phrases, default_xover_phrases); } if(retval == 0) { retval = do_one_array(fpo, nr_xover_reasons, default_xover_reasons); } fclose(fpo); if(retval == -1 ) { perror(argv[1]); } else { printf("Created %s\n", argv[1]); } } exit(retval); } /*------------------------------------------------------------------------*/ int do_one_array(FILE *fpo, int nr, const char **phrases) { int i, x; int retval = 0; for(i=0;i maxvar) { maxvar = i; } i = count_vars(default_test_phrases, nr_test_phrases); if( i > maxvar) { maxvar = i; } i = count_vars(default_suck_phrases, nr_suck_phrases); if( i > maxvar) { maxvar = i; } i = count_vars(default_timer_phrases, nr_timer_phrases); if( i > maxvar) { maxvar = i; } i = count_vars(default_chkh_phrases, nr_chkh_phrases); if( i > maxvar) { maxvar = i; } i = count_vars(default_dedupe_phrases, nr_dedupe_phrases); if( i > maxvar) { maxvar = i; } i = count_vars(default_killf_reasons, nr_killf_reasons); if( i > maxvar) { maxvar = i; } i = count_vars(default_killf_phrases, nr_killf_phrases); if( i > maxvar) { maxvar = i; } i = count_vars(default_sucku_phrases, nr_sucku_phrases); if( i > maxvar) { maxvar = i; } i = count_vars(default_lmove_phrases, nr_lmove_phrases); if( i > maxvar) { maxvar = i; } i = count_vars(default_active_phrases, nr_active_phrases); if(i > maxvar) { maxvar = i; } i = count_vars(default_batch_phrases, nr_batch_phrases); if(i > maxvar) { maxvar = i; } i = count_vars(default_xover_phrases, nr_xover_phrases); if(i > maxvar) { maxvar = i; } i = count_vars(default_xover_reasons, nr_xover_reasons); if(i > maxvar) { maxvar = i; } fprintf(fpo, "/* AUTOMATICALLY GENERATED BY makephrases */\n\n"); fprintf(fpo, "#define NR_BOTH_PHRASES %d\n", nr_both_phrases); fprintf(fpo, "#define NR_RPOST_PHRASES %d\n", nr_rpost_phrases); fprintf(fpo, "#define NR_TEST_PHRASES %d\n", nr_test_phrases); fprintf(fpo, "#define NR_SUCK_PHRASES %d\n", nr_suck_phrases); fprintf(fpo, "#define NR_TIMER_PHRASES %d\n", nr_timer_phrases); fprintf(fpo, "#define NR_CHKH_PHRASES %d\n", nr_chkh_phrases); fprintf(fpo, "#define NR_DEDUPE_PHRASES %d\n", nr_dedupe_phrases); fprintf(fpo, "#define NR_KILLF_REASONS %d\n", nr_killf_reasons); fprintf(fpo, "#define NR_KILLF_PHRASES %d\n", nr_killf_phrases); fprintf(fpo, "#define NR_KILLP_PHRASES %d\n", nr_killp_phrases); fprintf(fpo, "#define NR_SUCKU_PHRASES %d\n", nr_sucku_phrases); fprintf(fpo, "#define NR_LMOVE_PHRASES %d\n", nr_lmove_phrases); fprintf(fpo, "#define NR_ACTIVE_PHRASES %d\n", nr_active_phrases); fprintf(fpo, "#define NR_BATCH_PHRASES %d\n", nr_batch_phrases); fprintf(fpo, "#define NR_XOVER_PHRASES %d\n", nr_xover_phrases); fprintf(fpo, "#define NR_XOVER_REASONS %d\n", nr_xover_reasons); fprintf(fpo, "#define PHRASES_MAX_NR_VARS %d\n\n", maxvar); fprintf(fpo, "extern char *default_both_phrases[];\n"); fprintf(fpo, "extern char *default_rpost_phrases[];\n"); fprintf(fpo, "extern char *default_test_phrases[];\n"); fprintf(fpo, "extern char *default_suck_phrases[];\n"); fprintf(fpo, "extern char *default_timer_phrases[];\n"); fprintf(fpo, "extern char *default_chkh_phrases[];\n"); fprintf(fpo, "extern char *default_dedupe_phrases[];\n"); fprintf(fpo, "extern char *default_killf_reasons[];\n"); fprintf(fpo, "extern char *default_killf_phrases[];\n"); fprintf(fpo, "extern char *default_killp_phrases[];\n"); fprintf(fpo, "extern char *default_sucku_phrases[];\n"); fprintf(fpo, "extern char *default_lmove_phrases[];\n"); fprintf(fpo, "extern char *default_active_phrases[];\n"); fprintf(fpo, "extern char *default_batch_phrases[];\n"); fprintf(fpo, "extern char *default_xover_phrases[];\n"); fprintf(fpo, "extern char *default_xover_reasons[];\n"); fprintf(fpo, "extern char **both_phrases;\n"); fprintf(fpo, "extern char **rpost_phrases;\n"); fprintf(fpo, "extern char **test_phrases;\n"); fprintf(fpo, "extern char **suck_phrases;\n"); fprintf(fpo, "extern char **timer_phrases;\n"); fprintf(fpo, "extern char **chkh_phrases;\n"); fprintf(fpo, "extern char **dedupe_phrases;\n"); fprintf(fpo, "extern char **killf_reasons;\n"); fprintf(fpo, "extern char **killf_phrases;\n"); fprintf(fpo, "extern char **killp_phrases;\n"); fprintf(fpo, "extern char **sucku_phrases;\n"); fprintf(fpo, "extern char **lmove_phrases;\n"); fprintf(fpo, "extern char **active_phrases;\n"); fprintf(fpo, "extern char **batch_phrases;\n"); fprintf(fpo, "extern char **xover_phrases;\n"); fprintf(fpo, "extern char **xover_reasons;\n"); fclose(fpo); printf("Created phrases.h\n"); } return retval; } /*--------------------------------------------------------------*/ /* find highest nr of vars in any string in the array */ /* var == %v#% */ /*--------------------------------------------------------------*/ int count_vars(const char **phrases, int nr) { int i, x, max, nrline, match, nrat, len; max = 0; for(i=0;i x+2) && (phrases[i][nrat] == PHRASES_SEPARATOR)) { match = 1; nrline++; x = nrat+1; } } if (match == 0) { x++; } } if(nrline > max) { max = nrline; } } return max; } suck-4.3.4/man/000077500000000000000000000000001333033562000132405ustar00rootroot00000000000000suck-4.3.4/man/lmove.1000066400000000000000000000116721333033562000144530ustar00rootroot00000000000000.\" $Revision: 3.10.4 $ .TH LMOVE 1 .SH NAME lmove - move articles into /news/group/number directories .SH SYNOPSIS .I lmove .BI -d directory [ .BI -c filename ] [ .BI -a filename ] [ .BI -e | -E filename ] [ .BI -l phrase_file ] [ .BI -D ] [ .BI -A ] [ .BI -h | -s ] .SH OPTIONS \-a filename This is the location of the active file. See description of file below. If this option is not provided, the default of "active" is used. \-A Lmove uses the active file to determine what number to start at when creating the file name for the articles in each group. If an external program or person either puts articles into the directories, or changes the numbers in the active file, the possibility exists to accidently overwrite already existing articles. In order to avoid this, lmove checks to see if an article already exists before moving a new article into the directory tree. If it already exists, then lmove aborts with a warning message at that point. This option overrides the default action. If this option is given when lmove runs, then lmove will keep increasing the article number until it finds one not being used. \-c filename This is the location of the configuration file. See description of file below. If this option is not provided, the default of "lmove-config" is used. \-d directory This option is required. This is the directory that contains the articles for lmove to put into the directory structure. It should be on the same filesystem as the BASE directory (see ACTIVE FILE below), since some ..IX's move command can not move files across file systems. \-D This option tells lmove to log various debugging messages to debug.suck. This is primarily used by the developer to trace various problems. \-e | \-E filename These options will send all error messages (normally displayed on stderr), to an alternate file. The lower case version, -e, will send the error messages to the compiled-in default defined in suck_config.h. The upper case version, -E, requires the filename parameter. All error messages will then be sent to this file. \-l phrase_file This option tells lmove to load in an alternate phrase file, instead of using the built-in messages. This allows you to have lmove print phrases in another language, or to allow you to customize the messages without re-building lmove. See the "FOREIGN LANGUAGE PHRASES" in suck.1 for more details. \-s This option tells suck to create symbolic links for articles that are cross posted to multiple groups. The first group on the newsgroups line that is in the active file gets the actual text of the article, any other groups that are on the newsgroups line that also exist in the active file will get symbolic links to the actual text. This is so that news readers can see cross posted articles in all the groups that they were cross posted to. NOTE: If an article is cross posted to a group that does not exist in the active file, then a link will not be created. \-h This option is identical to the -s option, but instead of symbolic links, hard links are created. See man 2 link and man 2 symlink to explain the differences between hard and symbolic links. .SH DESCRIPTION Lmove will take articles in a single directory (such as those retrieved with "suck" ), and put them into a directory tree based on newsgroups. Lmove uses an "active" file to determine where to put the various articles, and to keep track of the highest numbered articles in these directories. Lmove will scan each article to find a matching group in the active file, then store the article in that group's directory, increasing the highest number for that group. Normally, once the first group of an article is matched in the active file, lmove goes on to the next article, unless you use the -h or -s option above. .SH CONFIGURATION FILE The configuration file should contain two lines: .RS BASE=/usr/spool/news .RE .RS ACTIVE=/usr/spool/news/active .RE The BASE= tells lmove the base directory for all articles. This is where the articles are actually stored. The ACTIVE= tells lmove where to find the active file, described below. .SH ACTIVE FILE The active file consists of newsgroup names, the current highest article number, the current lowest article number, and the current status of the group. Lmove only uses the newsgroup name and highest article number. The other fields are just rewritten, and not modified in any way. These are here for use by other programs. Example: .RS comp.os.linux.announce 1000 1 y .RE The lines are a listing of the valid groups that lmove will store articles in. The highest article number for a new group should be either 0 or 1. Upon completion, lmove will move the current active file to "active.old", and write out a new active file with the new highest article numbers. Any articles not moved into the directory structure are left in the original article directory. .SH EXIT VALUES 0 on success, -1 on failure. .de R$ Revision \\$$3, \\$$4 .. .SH "SEE ALSO" suck(1), rpost(1), testhost(1). suck-4.3.4/man/lpost.1000066400000000000000000000016731333033562000144720ustar00rootroot00000000000000.\" $Revision: 1.2 $ .TH LPOST 1 .SH NAME lpost - post articles to local server using rnews. .SH SYNOPSIS .I lpost [ .BI -v ] .SH OPTIONS -v verbose, show each article as it is uploaded. .SH NOTE Use the batch function of suck and avoid lpost, this is not the most efficient way of doing things. It is kept solely as a link to the past, since some people still use it. It is not compiled nor installed by default. To build and install lpost, from the source directory, run: .RS ./configure .RE .RS make install_lpost .RE .SH DESCRIPTION .I lpost (local post) reads stdin and pipes the articles to .I rnews, typically found in /usr/lib/news. If it is located elsewhere, change .I #define RNEWS in .I suck_config.h. A typical way to use lpost is: .RS zcat output.gz | lpost .RE This could take a while, so be patient. .SH EXIT VALUES 0 on success, -1 on failure. .de R$ Revision \\$$3, \\$$4 .. .SH "SEE ALSO" suck(1), rpost(1), testhost(1). suck-4.3.4/man/rpost.1000066400000000000000000000262141333033562000144760ustar00rootroot00000000000000.\" $Revision: 3.10.2 $ .TH RPOST 1 .SH NAME rpost - post an article to an NNTP news server .SH SYNOPSIS .I rpost [ .BI hostname ] [ .BI @filename ] [ .BI -s\ |\ -S filename ] [ .BI -e\ |\ -E filename ] [ .BI -b batchfile ] [ .BI -r rnews_file rnews_path ] [ .BI -p prefix ] [ .BI -Q ] [ .BI -d ] [ .BI \-U userid ] [ .BI \-P password ] [ .BI \-M ] [ .BI \-N port_number ] [ .BI \-l phrase_file ] [ .BI \-D ] [ .BI \-T timeout ] [ .BI \-u ] [ .BI \-n ] [ .BI -F perl_file ] [ .BI -i ] [ .BI -z ] [ .BI -f filter $$o= filter_arg1 ... ] .SH DESCRIPTION .I Rpost will post one or more articles, specified by .I hostname. If hostname is not specified, rpost will use the environment variable .BI NNTPSERVER. The hostname may optionally include the port number, in the form .BI Host:Port If this form is used, any port number specified via the -N option will be ignored. .SH Generic Options .BI \@filename This option tells rpost to read other options from a file in addition to the command line. .BI \-D This option tells rpost to log various debugging messages to "debug.suck", primarily for use by the developer. .BI \-e | \-E filename These options will send all error messages (normally displayed on stderr), to an alternate file. The lower case version, -e, will send the error messages to the compiled-in default defined in suck_config.h. The default is suck.errlog. The upper case version, -E, requires the filename parameter. All error messages will then be sent to this file. .BI \-i This option tells rpost to ignore the 201 (no posting allowed) from the welcoming message and to try and post anyway. Some news servers (inn-2.3.1) send 201 no posting allowed when they are using the AUTHINFO commands to verify permission to post. .BI \-l phrase_file This option tells rpost to load in an alternate phrase file, instead of using the built-in messages. This allows you to have rpost print phrases in another language, or to allow you to customize the messages without re-building. See the "FOREIGN LANGUAGE PHRASES" in suck.1 for more details. .BI \-M This option tells rpost to send the "mode reader" command to the remote server. If you get an invalid command message from rpost immediately after the welcome announcement, then try this option. .BI \-n This option tells rpost to show the name of the file as it is being uploaded. .BI \-N port_number This option tells rpost to use an alternate NNRP port number when connecting to the host, instead of the default port number, 119. .BI \-s | \-S filename These options will send all status messages (normally displayed on stdout), to an alternate file. The lower case version, -s, will send the status messages to the compiled-in default defined in suck_config.h. The default is /dev/null, so no status messages will be displayed. The upper case version, -S, requires the filename parameter. All status messages will then be sent to this file. .BI \-T This option overrides the compiled-in TIMEOUT value. This is how long rpost waits for data from the remote host before timing out and aborting. .BI \-u This option tells rpost to send the AUTHINFO USER command immediately upon connect to the remote server, rather than wait for a request for authorization. You must supply the \-U and \-P options when you use this option. .BI \-U userid .BI \-P password These two options let you specify a userid and password, if your NNTP server requires them. .BI \-Q This option allows you to specify the userid and password via the environment variables "NNTP_USER" and "NNTP_PASS" instead of on the command line. This prevents a potential security issue where someone doing a ps command can see your login and password. .BI \-z This option tells rpost to use SSL to communicate with the remote hosts, if SSL was compiled into rpost. .SH STDIN MODE rpost rpost hostname rpost reads one article from stdin and sends it to the NNTP server. The article must have a header of at least two lines, namely Newsgroups: and Subject: and a body (the article). Header and body have to be separated by a newline. Rpost does not change the article in any way. Rpost uses the POST command to post your article, just like any standard newsreader. This is handy when using SLIP and PPP, since most providers do not allow any other method to post articles (such as nntpsend or innxmit). .SH BATCH MODE .I rpost .BI hostname .BI -b batchfile .BI -p prefix .BI -d This batch mode allows you to give rpost a list of articles, and have them all posted. -b batchfile A listing of the articles to be posted. This parameter is REQUIRED. This file contains one article per line, with the line being the path to the file containing the article. For example: .RS -b /usr/spool/news/out.going/pixi .RE IF there are any problems uploading a specific article, a "failed" file will be created. It will be called "batchfile".fail, and contain the line from this batchfile for the article(s) that did not successfully upload. This file can be used to re-run the failed messages through rpost. NOTE: duplicate articles are NOT considered an error for the fail file. -d If the upload of articles is successful, this option will cause rpost to delete the batchfile named in the -b option. -p prefix If the batchfile does not contain a full path, but rather a partial path, this parameter must be specified. This is useful when the batch file is generated by another program. For example, Inn lists the path in the out.going file relative to its base directory /usr/spool/news. In that case just use: .RS -p /usr/spool/news .RE .SH RNEWS MODE .I rpost .BI hostname .BI -r rnews_file rnews_path This option allows you to use rnews generated file(s) to post. It requires two parameters. rnews_file - this is the base name for the rnews files. If you have your rnews file(s) called batch1, batch2, etc., then this argument would be "batch". rnews_path - this is the path to the location of the rnews files. .RS -r batch /usr/tmp/rnews .RE -d If the upload of all the articles from any of the rnews files is successful, then this option will cause rpost to delete that particular rnews file. .SH FILTER MODES -f filter $$o= filter_arg1 filter_arg2 ... In many cases, each article must be massaged before the remote NNTP will accept it. This option, and the embedded perl filter option below, lets you do that. These filters do not work in STDIN mode, but in the batch and rnews modes from above. Note that the -f .... option must be the LAST option, as everything that follows it is passed to the filter, except as noted below. There are three required parameters with this: $$o= - is the name of the file produced by your filter that will get uploaded to the remote NNTP server. THIS IS NOT passed to your filter program. This can be specified anywhere on the command line AFTER the -f filter argument, either before the filter name, or after it. filter - name of the program to call. Whatever follows filter, EXCEPT for the $$o, are arguments passed to the filter. arg1 - The first argument to your filter program/script. It most likely will be $$i, which rpost fills in with the name of the article that needs to be cleaned up. arg2 ... - any additional args needed can be specified. .PP Let's clarify this a bit with an example. Some NNTP servers don't like to receive articles with the NNTP-Posting-Host filled in. Create a short shell script to delete this from a file: \-myscr-------------------------------------------- #!/bin/sh sed -e "/^NNTP-Posting-Host/d" $1 > $2 \-end myscr---------------------------------------- .PP Then call rpost like this: .RS rpost localhost -b /usr/spool/news/out.going/pixi -f myscr \\$\\$o=/tmp/FILTERED_MSG \\$\\$i /tmp/FILTERED_MSG .RE Then, before each article is uploaded, myscr is called like such: .RS myscr infilename /tmp/FILTERED_MSG .RE After myscr has finished, rpost uploads the cleaned up article, stored in /tmp/FILTERED_MSG, to the remote NNTP server. .SH NOTE: The $$o and $$i have to be escaped, using either the backslashes as above, or with single quotes, to prevent the shell from trying to interpret these as variables. Failure to escape them will result in rpost not working! .BI -F perlfilter .PP This option allows you to use an embedded perl filter to filter your articles. In order to use this, you must edit the Makefile, and define the various PERL_ options. It has a couple of advantages over the -f option above. Because it is embedded perl, there are no forks and execls() done, so it should be faster. Also, you don't need to worry about the arguments to the program and escaping $$, etc as above. .PP Rpost will, when it starts up, load in the perlfilter file designated and parse it for syntax errors. Then, for each article to be uploaded, rpost will call the subroutine "perl_rpost", contained in the perlfilter file. See sample/put.news.pl for a complete working example. There are three key points you need to be aware of when creating your filter. .RS 1. The perlfilter file must contain the line "package Embed::Persistant;", so that variables in the perlfilter file don't clash with rpost variables, and the subroutine must be called "perl_rpost". This can be changed by editting the PERL_RPOST_SUB define in suck_config.h. .RE .RS 2. The perl_rpost subroutine receives the input file name as its sole argument, and must return the full path to the location of the filtered article as a single scalar string (return $outfile). .RE .RS 3. The subroutine must explicitly close the output file (containing the filtered argument) before it returns. This is because perl will only do an automatic close upon program completion (in our case when rpost exits), or when the file handle is reused (the next time the subroutine is called). If the close is not done, then more than likely, a 0 byte file will exist when rpost tries to post the article, and errors will result. .SH WARNING: Be very careful with what the filter program deletes from the article. Deleting the wrong line can have bad effects later on. For example, do not delete the MSG-ID line, as this could cause a single message to be posted many times, depending on the configuration of both the local and remote newserver. .SH RPOST ARGUMENT FILE .PP If you specify @filename on the command line, rpost will read from filename and parse it for any arguments that you wish to pass to rpost. You specify the same arguments in this file as you do on the command line. The arguments can be on one line, or spread out among more than one line. You may also use comments. Comments begin with '#' and go to the end of a line. All command line arguments override arguments in the file. One advantage to using the file instead of the command line, is that you don't have to escape any special characters, such as $. .RS # Sample Argument file .RE .RS -b batch # batch file option .RE .RS -M # use mode reader option .RE .SH EXIT VALUES Rpost returns the following exit values: .RS 0 = success .RE .RS 1 = error posting an article .RE .RS 2 = unable to do NNTP authorization with the remote server. .RE .RS 3 = unexpected answer to command when doing NNTP authorization. .RE .RS -1 = other fatal error. .RE .de R$ This is revision \\$3, \\$4. .. .SH "SEE ALSO" suck(1), testhost(1), lpost(1). suck-4.3.4/man/suck.1000066400000000000000000001172031333033562000142730ustar00rootroot00000000000000.\" $Revision: 4.2.0 $ .TH SUCK 1 .SH NAME suck - Pull a small newsfeed from an NNTP server, avoiding the NEWNEWS command. .SH SYNOPSIS .I suck [ .BI hostname ] [ .BI @filename ] [ .BI \-V ] [ .BI \-K ] [ .BI \-L[SL] ] [ .BI \-LF filename ] [ .BI \-H ] [ .BI \-HF filename ] [ .BI \-d[tmd] dirname ] [ .BI \-s\ |\ \-S filename ] [ .BI \-e\ |\ \-E filename ] [ .BI \-a ] [ .BI \-m ] [ .BI \-b[irlf] batchfile ] [ .BI \-r filesize ] [ .BI \-p extension ] [ .BI \-U userid ] [ .BI \-P password ] [ .BI \-Q ] [ .BI \-c ] [ .BI \-M ] [ .BI \-N port_number ] [ .BI \-W pause_time pause_nr_msgs ] [ .BI \-w pause_time pause_nr_msgs ] [ .BI \-l phrase_file ] [ .BI \-D ] [ .BI \-R ] [ .BI \-q ] [ .BI \-C count ] [ .BI \-k ] [ .BI \-A ] [ .BI \-AL activefile ] [ .BI \-hl localhost ] [ .BI \-bp ] [ .BI \-T timeout ] [ .BI \-n ] [ .BI \-u ] [ .BI \-z ] [ .BI \-x ] [ .BI \-B ] [ .BI \-O ] [ .BI \-G ] [ .BI \-X ] [ .BI \-f ] [ .BI \-y post_filter ] [ .BI \-F ] [ .BI \-g ] [ .BI \-i number_to_read ] [ .BI \-Z ] [ .BI \-rc ] [ .BI \-lr ] [ .BI \-sg ] [ .BI \-ssl ] [ .BI \-SSL ] Options valid in all modes \hostname The hostname may optionally include the port number, in the form .BI Host:Port. If this option is used, any port number specified via the -N option is ignored. \@filename This option tells suck to read other options from a file in addition to the commandline. \-a This option forces suck to always batch up any downloaded articles, even if suck aborts for any reason. Without this option, suck will only batch up articles if it finishes successfully or is cancelled by a signal (see below). \-A This option tells suck to scan the localhost (specified with the \-hl option) and use its active file to build and update the sucknewsrc. If you add a group to your local server, suck will add it to sucknewsrc and download articles. Or, if you delete a group from your local server, it will be deleted from sucknewsrc. If posting is not allowed to a particular group, then the line in sucknewsrc is just commented out. With this option, you should never have to edit your sucknewsrc. In case you have newsgroups (like control and junk) that you don't want downloaded, you can put these newsgroups in a file "active-ignore", one per line, and suck will ignore these newsgroups when it scans the localhost. If your system supports regex(), you may use regular expressions in the active-ignore file to skip multiple groups, eg: fred\.*. If you use the -p (postfix) option, suck will check for the existence of an active-ignore file with the postfix. If that doesn't exist, then suck will check for the existence of the file without the postfix. NOTE: If the localhost is on a non-standard port, the port number may be specified as part of the hostname, in the form .BI Host:Port. NOTE: If you use regular expressions, suck will silently add a "^" to the beginning of the group name, and a "$" to the end of the group name if they aren't already present, so that if you have "comp.os.linux", it won't match "comp.os.linux.answers" or if you have "alt.test" it doesn't match "comp.alt.test". \-AL activefile This option is identical to the -A option, except it reads the active file from the local file specified instead of reading it from the localhost. All the caveats from the -A option apply to this option as well. If both options are used on the command line, suck first tries to use the -A option, then if that fails it uses this option. \-B This option tells suck to attempt to batch up any articles in its directory BEFORE starting to download messages. This can be useful if you have a problem with the previous download. This option will only work if you specify a batch option (see below). If there are no messages to batch up, some of the batch options may produce warning messages. They may be safely ignored. Also, if the batch files exist at the end of the run, in inn-batch mode, it will be overwritten, since the new batch file will contain all messages. In rnews mode, if the batch file exists, it will abort and not batch up any messages. \-c If this option is specified, suck will clean up after itself. This includes: .RS 1. Moving sucknewsrc to sucknewsrc.old .RE .RS 2. Moving suck.newrc to sucknewsrc .RE .RS 3. rm suck.sorted and suckothermsgs. .RE \-C count This option tells suck to drop the connection and reopen it every count number of articles. This is designed to battle INN's LIKE_PULLERS=DONT option, that some folks compile in. With LIKE_PULLERS=DONT, after 100 messages INN will pause between every message, dramatically reducing your download speed. I don't recommend the use of this, but if you have no other choice.... \-dd dirname \-dm dirname \-dt dirname Specify the location of the various files used by suck. \-dd dirname = directory of data files used by suck (sucknewsrc suckkillfile suckothermsgs active-ignore sucknodownload) \-dm dirname = directory for storage of articles created in Multifile mode or batch mode. DO NOT make this the same as the directories used for the \-dt or -\dd options, or you will lose all your configuration files. \-dt dirname = directory of temp files created by suck (suck.newrc, suck.sort, suck.restart, suck.killlog, suck.post). \-D This option tells suck to log various debugging messages to "debug.suck", primarily for use by the maintainer. \-e | \-E filename These options will send all error messages (normally displayed on stderr), to an alternate file. The lower case version, -e, will send the error messages to the compiled-in default defined in suck_config.h. The default is suck.errlog. The upper case version, -E, requires the filename parameter. All error messages will then be sent to this file. \-f This option tells suck to reconnect after deduping, and before downloading the articles. This is in case long dedupe times cause timeouts on the remote end. \-F This option tells suck to reconnect after reading the local active file, and before downloading the Msg-IDs. This is in case of a large active file, which causes timeouts on the remote end. \-g This option causes suck to only download the headers of any selected articles. As a result of this, any batching of articles is skipped. This option does work with killfiles, however, killfile options such as BODYSIZE> will be ignored, since the body of the article will never be downloaded. \-G This option causes suck to display the message count and BPS status lines in a slightly different format, more suitable for use by a filter program (such as a GUI). \-H This option will cause suck to bypass the history check. \-HF history_file_name This option tells suck the location of the history file. The default is at /var/lib/news/history. \-hl localhost This option specifies the localhost name. This option is required with both the \-A and the \-bp option. \-i number_to_read This option tells suck the number of articles to download if you are using the -A or -AL option, and a new group is added. The default is defined in suck_config.h (ACTIVE_DEFAULT_LASTREAD, currently -100). NOTE: This must be a negative number (eg -100, -50), or 0, to download all articles currently available in the group. \-k This option tells suck to NOT attach the postfix from the \-p option to the names of the killfiles, both the master killfile and any group files. This allows you to maintain one set of killfiles for multiple servers. \-K This option will cause suck to bypass checking the killfile(s). \-l phrase_file This option tells suck to load in an alternate phrase file, instead of using the built-in messages. This allows you to have suck print phrases in another language, or to allow you to customize the messages without re-building suck. See below. \-lr This option, is used in conjunction with the highest article option in the sucknewsrc, to download the oldest articles, vice the newest articles. See that section for more details. \-L This option tells suck to NOT log killed articles to suck.killlog. \-LF filename This option allows you to override the built-in default of "suck.killlog" for the file which contains the log entries for killed articles. \-LL This option tells suck to create long log entries for each killed article. The long entry contains the short log entry and the header for the killed message. \-LS This option tells suck to create short log entries for each killed article. The short entry contains which group and which pattern was matched, as well as the MsgID of the killed article. \-M This option tells suck to send the "mode reader" command to the remote server. If you get an invalid command message immediately after the welcome announcement, then try this option. \-n This option tells suck to use the article number vice the MsgId to retrieve the articles. This option is supposedly less harsh on the remote server. It can also eliminate problems if your ISP ages off articles quickly and you frequently get "article not found" errors. Also, if your ISP uses DNEWS, you might need this option so that it knows you're reading articles in a group. \-N port_number This option tells suck to use an alternate NNRP port number when connecting to the host, instead of the default, 119. \-O This option tells suck to skip the first article upon restart. This is used whenever there is a problem with an article on the remote server. For some reasons, some NNTP servers, when they have a problem with a particular article, they time out. Yet, when you restart, you're back on the same article, and you time out again. This option tells suck to skip the first article upon restart, so that you can get the rest of the articles. \-p extension This extension is added to all files so that you can have multiple site feeds. For example, if you specify -p .dummy, then suck looks for sucknewsrc.dummy, suckkillfile.dummy, etc, and creates its temp files with the same extension. This will allow you to keep multiple sucknewsrc files, one for each site. \-q This option tells suck to not display the BPS and article count messages during download. Handy when running suck unattended, such as from a crontab. \-R This option tells suck to skip a rescan of the remote newserver upon a restart. The default is to rescan the newserver for any new articles whenever suck runs, including restarts. \-rc This option tells suck to change its behavior when the remote server resets its article counters. The default behavior is to reset the lastread in sucknewsrc to the current high article counter. With this option, suck resets the lastread in sucknewsrc to the current low article counter, causing it to suck all articles in the group, and using the historydb routines to dedupe existing articles. \-s | \-S filename These options will send all status messages (normally displayed on stdout), to an alternate file. The lower case version, -s, will send the status messages to the compiled-in default defined in suck_config.h. The default is /dev/null, so no status messages will be displayed. The upper case version, -S, requires the filename parameter. All status messages will then be sent to this file. \-sg This option tells suck to add the name of the current group being downloaded, if known, to the BPS display. Typically the only time suck doesn't know the group name is if an article is downloaded via the suckothermsgs file. \-ssl This option tells suck to use SSL to talk to the remote server, if suck was compiled with SSL support. \-SSL This option tells suck to use SSL to talk to the local server, if suck was compiled with SSL support. \-T timeout This option overrides the compiled-in TIMEOUT value. This is how long suck waits for data from the remote host before timing out and aborting. The timeout value is in seconds. \-u This option tells suck to send the AUTHINFO USER command immediately upon connect to the remote server, rather than wait for a request for authorization. You must supply the \-U and \-P options when you use this option. \-U userid \-P password These two options let you specify a userid and password, if your NNTP server requires them. \-Q This option tells suck to get the userid and password for NNTP authentication from the environment variables "NNTP_USER" and "NNTP_PASS" vice the -U or -P password. This prevents a potential security problem where someone doing a ps command can see your userid and password. \-V This option will cause suck to print out the version number and then exit. \-w pause_timer pause_nr_msgs This option allows you to slow down suck while pulling articles. If you send suck a predefined signal (default SIGUSR1, see suck_config.h), suck will swap the default pause options (if specified by the -W option), with the values from this option. For example, you run suck with -w 2 2, and you send suck a SIGUSR1 (using kill), suck will then pause 2 seconds between every other message, allowing the server to "catch its breath." If you send suck another SIGUSR1, then suck will put back the default pause options. If no pause options were specified on the command line (you omitted -W), then suck will return to the default full speed pull. \-W pause_time pause_nr_msgs This option tells suck to pause between the download of articles. You need to specify how long to pause (in seconds), and how often to pause (every X nr of articles). Ex: \-W 10 100 would cause suck to pause for 10 seconds every 100 articles. Why would you want to do this? Suck can cause heavy loads on a remote server, and this pause allows the server to "catch its breath." \-x This option tells suck to not check the Message-IDs for the ending > character. This option is for brain dead NNTP servers that truncate the XHDR information at 72 characters. \-X This option tells suck to bypass the XOVER killfiles. \-y post_filter This option is only valid when using any of batch modes. It allows you to edit any or all of the articles downloaded before posting to the local host. See below for more details. \-z This option tells suck to bypass the normal deduping process. This is primarily for slow machines where the deduping takes longer than the download of messages would. Not recommended. \-Z This option tells suck to use the XOVER command vice the XHDR command to retrieve the information needed to download articles. Use this if your remote news server doesn't support the XHDR command. .SH LONG OPTION EQUIVALENTS .RS \-a \-\-always_batch .RE .RS \-bi \-\-batch-inn .RE .RS \-br \-\-batch_rnews .RE .RS \-bl \-\-batch_lmove .RE .RS \-bf \-\-batch_innfeed .RE .RS \-bp \-\-batch_post .RE .RS \-c \-\-cleanup .RE .RS \-dt \-\-dir_temp .RE .RS \-dd \-\-dir_data .RE .RS \-dm \-\-dir_msgs .RE .RS \-e \-\-def_error_log .RE .RS \-f \-\-reconnect_dedupe .RE .RS \-g \-\-header_only .RE .RS \-h \-\-host .RE .RS \-hl \-\-localhost .RE .RS \-k \-\-kill_no_postfix .RE .RS \-l \-\-language_file .RE .RS \-lr \-\-low_read .RE .RS \-m \-\-multifile .RE .RS \-n \-\-number_mode .RE .RS \-p \-\-postfix .RE .RS \-q \-\-quiet .RE .RS \-r \-\-rnews_size .RE .RS \-rc \-\-resetcounter .RE .RS \-s \-\-def_status_log .RE .RS \-sg \-\-show_group .RE .RS \-ssl \-\-use_ssl .RE .RS \-w \-\-wait_signal .RE .RS \-x \-\-no_chk_msgid .RE .RS \-y \-\-post_filter .RE .RS \-z \-\-no_dedupe .RE .RS \-A \-\-active .RE .RS \-AL \-\-read_active .RS .RE \-B \-\-pre-batch .RE .RS \-C \-\-reconnect .RE .RS \-D \-\-debug .RE .RS \-E \-\-error_log .RE .RS \-G \-\-use_gui .RE .RS \-H \-\-no_history .RE .RS \-HF \-\-history_file .RE .RS \-K \-\-killfile .RE .RS \-L \-\-kill_log_none .RE .RS \-LS \-\-kill_log_short .RE .RS \-LL \-\-kill_log_long .RE .RS \-M \-\-mode_reader .RE .RS \-N \-\-portnr .RE .RS \-O \-\-skip_on_restart .RE .RS \-P \-\-password .RE .RS \-Q \-\-password_env .RE .RS \-R \-\-no_rescan .RE .RS \-S \-\-status_log .RE .RS \-SSL \-\-local_use_ssl .RS \-T \-\-timeout .RE .RS \-U \-\-userid .RE .RS \-V \-\-version .RE .RS \-W \-\-wait .RE .RS \-X \-\-no_xover .RE .RS \-Z \-\-use_xover .RE .SH DESCRIPTION .SH MODE 1 \- stdout mode .RS %suck .RE .RS %suck myhost.com .RE .PP Suck grabs news from an NNTP server and sends the articles to stdout. Suck accepts as argument the name of an NNTP server or if you don't give an argument it will take the environment variable NNTPSERVER. You can redirect the articles to a file or compress them on the fly like "suck server.domain | gzip \-9 > output.gz". Now it's up to you what you do with the articles. Maybe you have the output already on your local machine because you used a slip line or you still have to transfer the output to your local machine. .SH MODE 2 \- Multifile mode .RS %suck \-m .RE .RS %suck myhost.com \-m .RE .PP Suck grabs news from an NNTP server and stores each article in a separate file. They are stored in the directory specified in suck_config.h or by the \-dm command line option. .SH MODE 3 \- Batch mode .RS %suck myhost.com \-b[irlf] batchfile .RE .RS or %suck myhost.com \-bp -hl localhost .RE .RS or %suck myhost.com \-bP NR -hl localhost .RE .RS %suck myhost.com \-b[irlf] batchfile .RE .PP Suck will grab news articles from an NNTP server and store them into files, one for each article (Multifile mode). The location of the files is based on the defines in suck_config.h and the command line \-dm. Once suck is done downloading the articles, it will build a batch file which can be processed by either innxmit or rnews, or it will call lmove to put the files directly into the news/group/number format. \-bi \- build batch file for innxmit. The articles are left intact, and a batchfile is built with a one\-up listing of the full path of each article. Then innxmit can be called: .RS %innxmit localhost batchfile .RE \-bl \- suck will call lmove to put the articles into news/group/number format. You must provide the name of the configuration file on the command line. The following arguments from suck are passed to lmove: .RS The configuration file name (the batchfile name provided with this option) .RE .RS The directory specified for articles (-dm or built-in default). .RE .RS The errorlog to log errors to (-e or -E), if provided on the command line. .RE .RS The phrases file (-l), if provided on the command line. .RE .RS The Debug option, if provided on the command line. .RE \-br \- build batch file for rnews. The articles are concatenated together, with the #!rnews size article separator. This can the be fed to rnews: .RS %rnews \-S localhost batchfile .RE \-r filesize specify maximum batch file size for rnews. This option allows you to specify the maximum size of a batch file to be fed to rnews. When this limit is reached, a new batch file is created AFTER I finish writing the current article to the old batch file. The second and successive batch files get a 1 up sequence number attached to the file name specified with the -br. Note that since I have to finish writing out the current article after reaching the limit, the max file size is only approximate. \-bf \- build a batch file for innfeed. This batchfile contains the MsgID and full path of each article. The main difference between this and the innxmit option is that the innfeed file is built as the articles are downloaded, so that innfeed can be posting the articles, even while more articles are downloaded. \-bp \- This option tells suck to build a batch file, and post the articles in that batchfile to the localhost (specified with the \-hl option). This option uses the IHAVE command to post all downloaded articles to the local host. The batch file is called suck.post, and is put in the temporary directory (-dt). It is deleted upon completion, as are the successfully posted articles. If the article is not wanted by the server (usually because it already exists on the server, or it is too old), the article is also deleted. If other errors occur, the article is NOT deleted. With the following command line, you can download and post articles without worrying if you are using INND or CNEWS. .RS %suck news.server.com -bp -hl localhost -A -c .RE \-bP NR \- This option works identically to \-bp above, except instead of waiting until all articles are downloaded, it will post them to the local server after downloading NR of articles. .RS %suck news.server.com -bP 100 -hl localhost -A -c .RE .SH SUCK ARGUMENT FILE .PP If you specify @filename on the command line, suck will read from filename and parse it for any arguments that you wish to pass to suck. You specify the same arguments in this file as you do on the command line. The arguments can be on one line, or spread out among more than one line. You may also use comments. Comments begin with '#' and go to the end of a line. All command line arguments override arguments in the file. .RS # Sample Argument file .RE .RS -bi batch # batch file option .RE .RS -M # use mode reader option .RE .SH SUCKNEWSRC .PP Suck looks for a file .I sucknewsrc to see what articles you want and which you already received. The format of sucknewsrc is very simple. It consists of one line for each newsgroup. The line contains two or three fields. The first field is the name of the group. The second field is the highest article number that was in the group when that group was last downloaded. The third field, which is optional, limits the number of articles which can be downloaded at any given time. If there are more articles than this number, only the newest are downloaded. If the third field is 0, then no new messages are downloaded. If the command line option \-lr is specified, instead of downloading the newest articles, suck will download the oldest articles instead. The fields are separated by a space. .RS comp.os.linux.announce 1 [ 100 ] .RE .PP When suck is finished, it creates the file suck.newrc which contains the new sucknewsrc with the updated article numbers. .PP To add a new newsgroup, just stick it in sucknewsrc, with a highest article number of \-1 (or any number less than 0). Suck will then get the newest X number of messages for that newsgroup. For example, a -100 would cause suck to download the newest 100 articles for that newsgroup. .PP To tell suck to skip a newsgroup, put a # as the first character of a line. .SH SUCKKILLFILE and SUCKXOVER There are two types of killfiles supported in suck. The first, via the file suckkillfile, kills articles based on information in the actual article header or body. The second, via the file suckxover, kills articles based on the information retreived via the NNTP command XOVER. They are implemented in two fundamentally different ways. The suckkillfile killing is done as the articles are downloaded, one at a time. The XOVER killing is done while suck is getting the list of articles to download, and before a single article is downloaded. You may use either, none or both type of killfiles. .SH SUCKKILLFILE and GROUP KEEP/KILLFILES If .I suckkillfile exists, the headers of all articles will be scanned and the article downloaded or not, based on the parameters in the files. If no logging option is specified (see the -L options above), then the long logging option is used. .PP Comments lines are allowed in the killfiles. A comment line has a "#" in the first position. Everything on a comment line is ignored. .PP Here's how the whole keep/delete package works. All articles are checked against the master kill file (suckkillfile). If an article is not killed by the master kill file, then its group line is parsed. If a group file exists for one of the groups then the article is checked against that group file. If it matches a keep file, then it is kept, otherwise it is flagged for deletion. If it matches a delete file, then it is flagged for deletion, otherwise it is kept. This is done for every group on the group line. .PP NOTES: With the exception of the USE_EXTENDED_REGEX parameter, none of these parameters are passed from the master killfile to the individual group file. Each killfile is separate and independant. Also, each search is case-insensitive unless specifically specified by starting the search string with the QUOTE character (see below). However, the parameter part of the search expression (the LOWLINE=, HILINE= part) is case sensitive. .SH PARAMETERS .RS LOWLINES=####### .RE .RS HILINES=####### .RE .RS NRGRPS=#### .RE .RS NRXREF=#### .RE .RS QUOTE=c .RE .RS NON_REGEX=c .RE .RS GROUP=keep groupname filename OR GROUP=delete groupname filename .RE .RS PROGRAM=pathname .RE .RS PERL=pathname .RE .RS TIEBREAKER_DELETE .RE .RS GROUP_OVERRIDE_MASTER .RE .RS USE_EXTENDED_REGEX .RE .RS XOVER_LOG_LONG .RE .RS HEADER: .RE .RS Any Valid Header Line: .RE .RS BODY: .RE .RS BODYSIZE> .RE .RS BODYSIZE< .RE .PP All parameters are valid in both the master kill file and the group files, with the exception of GROUP, PROGRAM, PERL, TIEBREAKER_DELETE, and GROUP_OVERRIDE_MASTER. These are only valid in the master kill file. .SH KILL/KEEP Files Parameters .PP .I HILINES= Match any article longer than the number of lines specified. .PP .I LOWLINES= Match any article shorter than the number of lines specified. .PP .I NRGRPS= This line will match any article which has more groups than the number specified on the Newsgroups: line. Typically this is used in a killfile to prevent spammed articles. (A spammed article is one that is posted to many many groups, such as those get-rich quick schemes, etc.) .PP .I NRXREF= This line will match any article that has more groups than than the number specified on the Xref: line. This is another spamm stopper. WARNING: the Xref: line is not as accurate as the Newsgroups: line, as it only contains groups known to the news server. This option is most useful in an xover killfile, as in Xoverviews don't typically provide the Newsgroups: line, but do provide the Xref: line. .PP .I HEADER: .I Any Valid Header Line: Suck allows you to scan any single header line for a particular pattern/string, or you may scan the entire article header. To scan an individual line, just specify it, for example to scan the From line for boby@pixi.com, you would put .RS From:boby@pixi.com .RE Note that the header line EXACTLY matches what is contained in the article. To scan the Followup-To: line, simply put \"Followup-To:\" as the parameter. To search the same header line for multiple search items, then each search item must be on a separate line, eg: .RS From:boby@xxx .RE .RS From:nerd@yyy .RE .RS Subject:suck .RE .RS Subject:help .RE The parameter HEADER: is a special case of the above. If you use the HEADER: parameter, then the entire header is searched for the item. You are allowed multiple HEADER: lines in each killfile. .PP When suck searches for the pattern, it only searches for what follows the :, and spaces following the : are significant. With the above example "Subject:suck", we will search the Subject header line for the string "suck". If the example had read "Subject: suck", suck would have searched for the string " suck". Note the extra space. .PP If your system has regex() routines on it, then the items searched for can be POSIX regular expressions, instead of just strings. Note that the QUOTE= option is still applied, even to regular expressions. .PP .I BODY: This parameter allows you to search the body of an article for text. Again, if your system has regex(), you can use regular expressions, and the QUOTE= option is also applied. You are allowed multiple BODY: lines in each killfile. WARNING: Certain regex combinations, especially with .* at the beginning, (eg BODY:.*jpg), in combination with large articles, can cause the regex code to eat massive amounts of CPU, and suck will seem like it is doing nothing. .PP .I BODYSIZE> This parameter will match an article if the size of its body (not including the header) is greater than this parameter. The size is specified in bytes. .PP .I BODYSIZE< This parameter will match an article if the size of its body, is less than this parameter. The size is specified in bytes. .PP .I QUOTE= This item specifies the character that defines a quoted string. The default for this is a ". If an item starts with the QUOTE character, then the item is checked as-is (case significant). If an item does not start with the QUOTE character, then the item is checked with out regard to case. .PP .I NON_REGEX= This items specifies the character that defines a non-regex string. The default for this is a %. If an item starts with the NON_REGEX character, then the item is never checked for regular expressions. If the item doesn't start with the QUOTE character, then suck tries to determine if it is a regular expression, and if it is, use regex() on it. This item is so that you can tell suck to treat strings like "$$$$ MONEY $$$$" as non-regex items. IF YOU USE BOTH QUOTE and NON_REGEX characters on a string, the NON_REGEX character MUST appear first. .PP .I GROUP= This line allows you to specify either keep or delete parameters on a group by group basis. There are three parts to this line. Each part of this line must be separated by exactly one space. The first part is either "keep" or "delete". If it is keep, then only articles in that group which match the parameters in the group file are downloaded. If it is delete, articles in that group which match the parameters are not downloaded. The second part, the group name is the full group name for articles to check against the group file. The group name may contain an * as the last character, to match multiple groups, eg: "comp.os.linux.*" would match comp.os.linux.announce, comp.os.linux.answers, etc.. The third part specifies the group file which contains the parameters to check the articles against. Note, that if you specified a postfix with the \-p option, then this postfix is attached to the name of the file when suck looks for it, UNLESS you use the \-k option above. .PP .I GROUP_OVERRIDE_MASTER This allows you to override the default behavior of the master kill file. If this option is in the master kill file, then even if an article is flagged for deletion by the master kill file, it is checked against the group files. If the group files says to not delete it, then the article is kept. .PP .I TIEBREAKER_DELETE This option allows you to override the built-in tie-breaker default. The potential exists for a message to be flagged by one group file as kept, and another group file as killed. The built-in default is to then keep the message. The TIEBREAKER_DELETE option will override that, and caused the article to be deleted. .PP .I USE_EXTENDED_REGEX This option tells suck to use extended regular expressions vice standard regular expressions. It may used in the master killfile, in which case it applies to all killfiles, or in an individual killfile, where it only applies to the parameters that follow it in the killfile. .PP .I XOVER_LOG_LONG This option tells suck to format the killfile generated by from an Xover killfile so that it looks like an article header. The normal output is to just print the Xover line from theserver. .PP .I PROGRAM= This line allows suck to call an external program to check each article. You may specify any arguments in addition to the program name on this line. If this line is in your suckkillfile, all other lines are ignored. Instead, the headers are passed to the external program, and the external program determines whether or not to download the article. Here's how it works. Suck will fork your program, with stdin and stdout redirected. Suck will feed the headers to your program thru stdin, and expect a reply back thru stdout. Here's the data flow for each article: .RS 1. suck will write a 8 byte long string, which represents the length of the header record on stdin of the external program. Then length is in ascii, is left-aligned, and ends in a newline (example: "1234 \\n"). .RE .RS 2. suck will then write the header on stdin of the external program. .RE .RS 3. suck will wait for a 2 character response code on stdout. This response code is either "0\\n" or "1\\n" (NOT BINARY ZERO OR ONE, ASCII ZERO OR ONE). If the return code is zero, suck will download the article, if it is one, suck won't. .RE .RS 4. When there are no more articles, the length written down (for step 1) will be zero (again in ascii "0 \\n"). Suck will then wait for the external program to exit before continuing on. The external program can do any clean up it needs, then exit. Note: suck will not continue processing until the external program exits. .RE .PP .I PERL= This line allows suck to call a perl subroutine to check each article. In order to use this option, you must edit the Makefile, specifically the PERL* options. If the PERL= line is in your suckkillfile, all other lines are ignored. Instead, the header is sent to your perl subroutine, and your subroutine determines if the article is downloaded or not. The parameter on the PERL= line specifies the file name of the perl routine eg: .RS PERL=perl_kill.pl .RE .PP See the sample/perl_kill.pl for a sample perl subroutine. There are a couple of key points in this sample. The "package Embed::Persistant;" must be in the perl file. This is so that any variable names you create will not conflict with variable names in suck. In addition, the subroutine you define must be "perl_kill", unless you change the PERL_PACKAGE_SUB define in suck_config.h. Also, your subroutine must return exactly one value, an integer, either 0 or 1. If the subroutine returns 0, then the article is downloaded, otherwise, the article is not downloaded. .PP NOTES: The perl file is only compiled once, before any articles are downloaded. This is to prevent lengthy delays between articles while the perl routine is re-compiled. Also, you must use Perl 5.003 or newer. In addition, you are advised to run 'perl -wc filter' BEFORE using your filter, in order to check for syntax errors and avoid problems. .SH SUCKXOVER If the file .I suckxover exists, then suck uses the XOVER command to get information on the articles and decide whether or not to download the article. Xover files use the same syntax as suckkillfiles, but supports a subset of the commands. .PP The following killfile commands are not supported in suckxover files: .RS NRGROUPS: .RE .RS HEADER: .RE .RS BODY: .RE .RS TIEBREAKER_DELETE: .RE .PP Only the following header lines will be checked: .RS Subject: .RE .RS From: .RE .RS Message-ID: .RE .RS References: .RE .PP The behaviour of the size commands ( .I BODYSIZE>, BODYSIZE<, HILINES, and LOWLINES ) specify the total size of the article (not just the body) in bytes or lines, respectively. .PP All other parameters are allowed. However, if you use an invalid parameter, it is silently ignored. .SH SUCKXOVER and PROGRAM= or PERL= parameters These parameters are supported in a suckxover file, however they work slightly differently than described above. The key difference is that prior to sending each individual xoverview line to your program, suck will send you the overview.fmt listing that it retrieves from the server. This overview.fmt is a tab-separated line, describing the fields in each overview.fmt line. .PP For the PROGRAM= parameter, suck will first send your program an 8 byte long string, which is the length of the overview.fmt. This length is formatted as the lengths above (see nr1 under PROGRAM=). Suck will then send the overview.fmt. After that, the flow is as described above. See sample/killxover_child.c for an example. .PP For the PERL= parameter, Your program must have two subroutines. The first is perl_overview, which will recieve the overview.fmt, and not return anything. The second subroutine is perl_xover, which will recieve the xoverview line, and return 0 or 1, as described in the PERL= above. See sample/perl_xover.pl for an example. .SH SUCKOTHERMSGS If .I suckothermsgs exists, it must contain lines formatted in one of three ways. The first way is a line containing a Message-ID, with the <> included, eg: .RS <12345@somehost.com> .RE This will cause the article with that Message-ID to be retrieved. .PP The second way is to put a group name and article number on a line starting with an !, eg: .RS !comp.os.linux.announce 1 .RE This will cause that specific article to be downloaded. .PP You can also get a group of articles from a group by using the following syntax: .RS !comp.os.linux.announce 1-10 .RE .PP Whichever method you use, if the article specified exists, it will be downloaded, in addition to any articles retreived via the .I sucknewsrc. These ways can be used to get a specific article in other groups, or to download an article that was killed. These articles .B ARE NOT processed through the kill articles routines. .SH SUCKNODOWNLOAD If .I sucknodownload exists, it must consist of lines contaning a Message-ID, with the <> included, eg: .RS <12345@somehost.com> .RE This will cause the article with that Message-ID to NEVER be downloaded. The Message-ID must begin in the first column of the line (no leading spaces). This file overrides .I suckothermsgs so if an article is in both, it will not be downloaded. .SH POST FILTER if the .BI "-y post_filter" option is specified on the command line in conjunction with any of the batch modes, then suck will call the post filter specified, after downloading the articles, and before batching/posting the articles. The filter is passed the directory where the articles are stored (the -dm option). The filter program is responsible for parsing the contents of the directory. See sample/post_filter.pl for a sample post filter. This option was designed to allow you to add your own host name to the Path: header, but if you need to do anything else to the messages, you can. .SH FOREIGN LANGUAGE PHRASES If the .BI "-l phrases" option is specified or the file /usr/local/lib/suck.phrases (defined in suck_config.h) exists, then suck will load an alternate language phrase file, and use it for all status & error messages, instead of the built-in defaults. The command line overrides the build in default, if both are present. The phrase file contains all messages used by suck, rpost, testhost, and lmove, each on a separate line and enclosed in quotes. To generate a sample phrase file, run .BI "make phrases" from the command line. This will create "phrases.engl", which is a list of the default phrases. Simply edit this file, changing the english phrases to the language of your choosing, being sure to keep the phrases within the quotes. These phrases may contain variables to print items provided by the program, such as hostname. Variables are designated by %vN% where N is a one-up sequence per phrase. These variables may exist in any order on the phrase line, for example, .RS "Hello, %v1%, welcome to %v2%" or .RE .RS "Welcome to %v2%, %v1%" .RE are both valid phrases. Phrases may contain, \\n, \\r, or \\t to print a newline, carriage return, or tab, respectively. Note that the first line of the phrase file is the current version number. This is checked against the version of suck running, to be sure that the phrases file is the correct version. If you modify any of the source code, and add in new phrases, you will need to regenerate phrases.h, so that everything works correctly. To recreate, just run .BI "make phrases.h" from the command line. .SH SIGNAL HANDLING Suck accepts two signals, defined in .I suck_config.h. The first signal (default SIGTERM) will cause Suck to finish downloading the current article, batch up whatever articles were downloaded, and exit, without an error. The second signal (default SIGUSR1) will cause suck to use the pause values defined with the -w option (see above). .SH EXIT CODES Suck will exit with the following return codes: .RS 0 = success .RE .RS 1 = no articles available for download. .RE .RS 2 = suck got an unexpected answer to a command it issued to the remote server. .RE .RS 3 = the -V option was used. .RE .RS 4 = suck was unable to perform NNTP authorization with the remote server. .RE .RS -1 = general error. .RE .SH HISTORY .RS Original Author - Tim Smith (unknown address) .RE .RS Maintainers - .RE .RS March 1995 - Sven Goldt (goldt@math.tu-berlin.de) .RE .RS July 1995 - Robert A. Yetman (boby@pixi.com) .RE .de R$ Revision \\$$3, \\$$4 .. .SH "SEE ALSO" testhost(1), rpost(1), lpost(1). suck-4.3.4/man/testhost.1000066400000000000000000000073751333033562000152130ustar00rootroot00000000000000.\" $Revision: 3.10.0 $ .TH TESTHOST 1 .SH NAME testhost - test the status of an NNTP news server .SH SYNOPSIS .I testhost .BI hostname [ .BI -a | -n date time .BI | -o ] [ .BI -M ] [ .BI -s | -S filename ] [ .BI -e | -E filename ] [ .BI -N port_number ] [ .BI -U userid ] [ .BI -P password ] [ .BI -Q ] [ .BI -l phrase_file ] [ .BI -T timeout ] [ .BI -d ] [ .BI -q ] [ .BI -z ] .SH OPTIONS -a Get the active list from hostname \-d This option tells testhost get the descriptions of the newsgroups on the remote server by sending the 'list newsgroups' command. The remote server may or may not support this command. -e | -E filename These options will send all error messages (normally displayed on stderr), to an alternate file. The lower case version, -e, will send the error messages to the compiled-in default defined in suck_config.h. The default is suck.errlog. The upper case version, -E, requires the filename parameter. All error messages will then be sent to this file. \-l phrase_file This option tells testhost to load in an alternate phrase file, instead of using the built-in messages. This allows you to have testhost print phrases in another language, or to allow you to customize the messages without re-building. See the "FOREIGN LANGUAGE PHRASES" in suck.1 for more details. -n date time Get the newgroups created on the host since the date and time specified. The date must be in YYMMDD format and the time must be in HHMMSS format. -N port_number This option will tell testhost to use an alternate NNRP port number when connecting to the host, instead of the default, 119. \-q This option tells testhost to not display the connection and announcement messages, rather only display the results of the actual command run. -s | -S filename These options will send all status messages (normally displayed on stdout), to an alternate file. The lower case version, -s, will send the status messages to the compiled-in default defined in suck_config.h. The default is /dev/null, so no status messages will be displayed. The upper case version, -S, requires the filename parameter. All status messages will then be sent to this file. \-T This option overrides the compiled-in TIMEOUT value. This is how long testhost waits for data from the remote host before timing out and aborting. \-U userid \-P password These two options let you specify a userid and password, if your NNTP server requires them. \-Q This option tells testhost to use the environment variable NNTP_USER & NNTP_PASS to specify a userid and password, if your NNTP server requires them. This option is provided so that the userid & password can't be seen with the ps command, a potential security problem. \-z This options tells testhost to use SSL to talk to the remote server, if testhost was compiled with SSL. .SH DESCRIPTION .I Testhost will query a NNTP news server, specified by .I hostname. The hostname may optionally include the port number in the form .BI Host:Port. If the port number is included, the port number specified by the -N option will be ignored. The default action is to issue the .I help command to the server, to see what software it is running and what commands it accepts. .PP If the .I -a option is used, testhost will display the server's active history list. If the .I -n date time option is used, testhost will display all new groups created on the server since the date and time specified. If the .I -o option is used, then testhost will display the overview format, which is what the XOVER command returns. .PP If the .I -M option is used, the command will be preceded with the "mode reader" command, which might be needed by some servers. .SH EXIT VALUES 0 on success, -1 on failure. .de R$ This is revision \\$3, \\$4. .. .SH "SEE ALSO" suck(1), rpost(1), lpost(1). suck-4.3.4/perl/000077500000000000000000000000001333033562000134275ustar00rootroot00000000000000suck-4.3.4/perl/README000066400000000000000000000006141333033562000143100ustar00rootroot00000000000000This program will display a directory of article headers, one at a time, and allow you to add the Message-ID to suckothermsgs for forced downloading. This program, is written in Perl, and requires the Perl:Tk module available from http://www.perl.com/CPAN/modules/by-module/Tk/tk8XX.XXX.tar.gz 800.013 is the latest version as of 21 Mar 99. Sample Usage: suck remotehost -m -g header.pl Msgs suck-4.3.4/perl/header.pl000077500000000000000000000216421333033562000152240ustar00rootroot00000000000000#!/usr/bin/perl -w # See README for more info # This program uses two global lists # @msgids - the message ids to be written # @files - index from message-id to file, so can match msgid to file # things modifiable $OUTPUT="suckothermsgs"; $MSGID_FOREGROUND="white"; $MSGID_BACKGROUND="blue"; $LISTBOX_WIDTH=30; # width of both list boxes (file list and msgid list) $ARTICLE_WIDTH=80; # width of the article box use Tk; if ( $#ARGV != 0 ) { die "Usage: $0 path_to_headers \n"; } $dir = $ARGV[0]; # first read in list of articles opendir DIR, "$dir" or die "Can't open $dir\n"; @articles = sort(grep(/^\d+-\d+/, readdir(DIR))); closedir DIR; $count = @articles; # save nr of articles if($count > 0) { $mw = MainWindow->new; $mw->title("Suck's Article Viewer"); # set up our directory listing to select an article # the exportselection => 0 so both listboxes can have selections $frame=$mw->Frame()->pack(-side => "top"); $f1=$frame->Frame()->pack(-side => "left"); # for our two listboxes $f1->Label(-text => "Article Listing")->pack(-side => "top"); $listbox = $f1->Scrolled("Listbox", -scrollbars => "oe", -width => $LISTBOX_WIDTH, -exportselection => 0)->pack(-side => "top"); $listbox->insert('end', @articles); $listbox->bind("", \&article_selected); # so can select a new file $listbox->selectionSet(0); # so first item is selected and loaded # set up our listbox to show selected Message-IDs $f1->Label(-text => "Message-IDs Selected")->pack(-side => "top"); $msgidbox = $f1->Scrolled("Listbox", -scrollbars => "oe", -width => $LISTBOX_WIDTH, -exportselection => 0)->pack(-side => "top"); $msgidbox->bind("", \&msgid_selected); # so can go back to article #set up our article window $article = $frame->Scrolled("Text", -scrollbars => "osoe", -width => $ARTICLE_WIDTH, -wrap => "none")->pack(-side => "left"); $article->tagConfigure('Msgid', -foreground => $MSGID_FOREGROUND, -background => $MSGID_BACKGROUND); # so we can highlight message id &article_selected(); # this is here so that it can load the initial article, can't do this until article window is set up # set up our button window $bw=$mw->Frame()->pack(-side => "bottom"); # button window $bw->Button(-text => "Select Article", -command => \&select_msgid)->pack(-side => "left"); $bw->Button(-text => "Next", -command => \&next_one)->pack(-side => "left"); $bw->Button(-text => "Previous", -command => \&previous)->pack(-side => "left"); $bw->Button(-text => "De-Select Article", -command => \&deselect)->pack(-side => "left"); $bw->Button(-text => "Done", -command => \&save_msgids)->pack(-side => "left"); MainLoop; } else { print "No articles found\n"; } #-------------------------------------------------------------------------------------- sub article_selected() { # this routine displays the article selected into our article window my ( $msgid, @list, $file, $path, @spl, $found, $count); $msgid = 0; @list = $listbox->curselection(); # get the index of the current item selected $file = $listbox->get($list[0]); # clear out the old file $article->configure(-state => "normal"); # so we can insert $article->delete("1.0", "end"); # now load the file $path = "$dir/$file"; open FP, "<$path" or die "Can't read $path\n"; while () { # find the Message-ID and highlight it if (( $msgid == 0) && ( $_ =~ /^Message-ID:/) ) { @spl = split(/[<>]/, $_, 3); #split to get only the actual message id $article->insert('end', $spl[0]); $article->insert('end', "<${spl[1]}>", 'Msgid'); # tag ONLY the messageid, we need to add back the <> split took off $article->insert('end', $spl[2]); $msgid = 1; # so we only get Message-ID and not any extraneous stuff (like quoted articles) } else { $article->insert('end', $_); } } close FP; $article->configure(-state => "disabled"); # so user can't edit # now, see if we have a Message-ID for it, and if so, select it # @files is our index of files to msgid, go thru it and see if # our listbox index is in it $found = -1; if(defined @files) { $count = -1; # keep track of where we are in msgid list for( $count = 0 ; $count <= $#files ; $count++) { if($files[$count] == $list[0]) { $found = $count; last; } } # clear the current list @list = $msgidbox->curselection(); if(defined $list[0]) { $msgidbox->selectionClear($list[0]); } # show it if($found >= 0) { $msgidbox->selectionSet($found); $msgidbox->see($found); } } } #-------------------------------------------------------------------------------------- sub next_one() { my ( @list, $itemnr); # increment the listbox, and load it @list = $listbox->curselection(); $itemnr = $list[0] + 1; if( $itemnr < ($count - 1) ) { # the -1 because index start at 0 $listbox->selectionClear($itemnr - 1); $listbox->selectionSet($itemnr); $listbox->see($itemnr); &article_selected(); } } #-------------------------------------------------------------------------------------- sub previous() { my ( @list, $itemnr) ; # decrement the listbox and load it @list = $listbox->curselection(); $itemnr = $list[0] - 1; if( $itemnr >= 0 ) { $listbox->selectionClear($itemnr + 1); $listbox->selectionSet($itemnr); $listbox->see($itemnr); &article_selected(); } } #--------------------------------------------------------------------------------------- sub select_msgid() { # get the tagged Msgid from the text and add it to the listbox my ( @index, $msgid, $count, $file, @list, $tmp); @index = $article->tagRanges("Msgid"); $msgid = $article->get($index[0], $index[1]); # get only the first one, just in case more than one got selected # make sure we don't already have it, can't use grep cause of the wacky chars in the msgid $count = 0; foreach $tmp ( @msgids ) { if ( $tmp eq $msgid ) { $count++; } } if($count == 0 ) { @list = $listbox->curselection(); # get the index of the current items selected push @files, $list[0]; # so if user clicks on a msg id, we can go to that article push @msgids, $msgid; # add to our list for later saving and making sure we don't already have it $msgidbox->insert('end', $msgid); $msgidbox->selectionSet('end'); $msgidbox->see('end'); } } #--------------------------------------------------------------------------------------- sub msgid_selected() { # selected a msgid, put that article back in the viewer my (@list, @l2); @list = $msgidbox->curselection(); @l2 = $listbox->curselection(); $listbox->selectionClear($l2[0]); # clear the current selection # @files is a list of indexes to files for the msgids, use it to go back to file $listbox->selectionSet($files[$list[0]]); # make this one the current selectio $listbox->see($files[$list[0]]); &article_selected(); # so it gets redisplayed } #---------------------------------------------------------------------------------------- sub deselect() { # if the current message id is selected, remove it from the list my @list; @list = $msgidbox->curselection(); # if its selected, it'll be here if(defined $list[0]) { $msgidbox->delete($list[0]); # remove from listbox splice @msgids, $list[0], 1; # remove it from list of msgids splice @files, $list[0], 1; # remove it from list of file indexes } } #-------------------------------------------------------------------------------------------- sub save_msgids() { # save the @msgids list to OUTPUT # pop up the filename query if( ! Exists($popup)) { $outfile = $OUTPUT; $popup = $mw->Toplevel(); $popup->title( "Save Message-IDs?"); $popup->Label(-text => "File Path")->pack(-side => "top"); $popup->Entry(-textvariable => \$outfile, -width => 50)->pack(-side => "top"); $buttons = $popup->Frame()->pack(-side => "top"); $buttons->Button(-text => "Yes", -command => \&save_file)->pack(-side => "left"); $buttons->Button(-text => "Cancel", -command => sub { $popup->destroy } )->pack(-side => "left"); $buttons->Button(-text => "No", -command => sub { exit })->pack(-side => "left"); } } #--------------------------------------------------------------------------------------------- sub save_file() { # save our output file my ( $item ) ; if(defined @msgids) { if ( open FPTR, ">$outfile" ) { foreach $item ( @msgids ) { print FPTR "${item}\n"; } close FPTR; exit; } else { errmsg("Unable to create $outfile"); } } } #----------------------------------------------------------------------------------------------- sub errmsg() { # any error message if ( ! Exists($errp)) { $errp = $mw->Toplevel(); $errp->Label(-text => $_[0])->pack(-side => "top"); $errp->Button(-text => "Okay", -command => sub { $errp->destroy })->pack(-side => "top"); } } suck-4.3.4/read_db.c000066400000000000000000000030411333033562000142070ustar00rootroot00000000000000#include #include #include #include #include #include #include "suck.h" /* this file reads in the suck.db file and writes it out in a more readable format */ int main(int argc, char *argv[]) { int fdin; const char *fin = NULL, *fout = NULL; FILE *fpout; List item; Groups grp; long count; int retval = 0; if(argc == 1) { fin = "suck.db"; fout = "suck.db.out"; } else if(argc != 3) { fprintf(stderr, "Usage: %s in_file out_file \n", argv[0]); retval = -1; } else { fin = argv[1]; fout = argv[2]; } if(retval == 0) { if((fdin = open(fin, O_RDONLY)) == -1) { perror(fin); retval = -1; } else if((fpout = fopen(fout, "w")) == NULL) { perror(fout); retval = -1; } else { /* read items */ read(fdin, &count, sizeof(long)); /* get item count */ fprintf(fpout, "%ld\n", count); while(count > 0 && read(fdin, &item, sizeof(item)) == sizeof(item)) { fprintf(fpout, "%s-%d-%ld-%ld-%c-%d-%d-%d\n", item.msgnr, item.groupnr, item.nr,item.dbnr,item.mandatory,item.downloaded, item.delete,item.sentcmd); count--; } if(count > 0) { perror(fin); retval = -1; } else { read(fdin, &count, sizeof(long)); /* get group count */ fprintf(fpout, "%ld\n", count); while(count >= 0 && read(fdin, &grp, sizeof(grp)) == sizeof(grp)) { fprintf(fpout, "%s-%d\n", grp.group, grp.nr); count--; } if(count >= 0) { perror(fin); retval = -1; } } } } return retval; } suck-4.3.4/rpost.c000066400000000000000000001031671333033562000140100ustar00rootroot00000000000000#include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef HAVE_LIMITS_H #include #endif #ifdef PERL_EMBED #include #include #ifdef OLD_PERL #ifndef ERRSV # define ERRSV (GvSV(errgv)) /* needed for perl 5.004 and earlier */ #endif #ifndef PL_na # define PL_na (na) #endif #endif /* OLD_PERL */ #endif #ifdef HAVE_DIRENT_H # include #else # define dirent direct # ifdef HAVE_SYS_NDIR_H # include # endif # ifdef HAVE_SYS_DIR_H # include # endif #endif #include "suck_config.h" #include "both.h" #include "phrases.h" struct nntp_auth { char *userid; char *passwd; int autoauth; int use_env; }; typedef struct { char *batch; char *prefix; char *host; char *filter_args[RPOST_MAXARGS]; int filter_argc; int filter_infilenr; /* nr of arg that we replace with infile */ char *filter_outfile; /* pointer to name of outfile */ int deleteyn; int do_modereader; int debug; struct nntp_auth auth; FILE *status_fptr; unsigned short int portnr; const char *phrases; char *rnews_file; char *rnews_path; int sockfd; int show_name; int ignore_readonly; int do_ssl; void *ssl_struct; #ifdef PERL_EMBED PerlInterpreter *perl_int; #endif } Args, *Pargs; /* function declarations */ int do_article(Pargs, FILE *); int do_batch(Pargs); int do_rnews(Pargs); char *do_filter(Pargs, char *); int send_command(Pargs, const char *, char **, int); int do_authenticate(Pargs); int scan_args(Pargs, int, char *[]); int filter_post_article(Pargs, char *); void log_fail(char *, char *); void load_phrases(Pargs); void free_phrases(void); int parse_filter_args(Pargs myargs, int, char **); #ifdef PERL_EMBED void parse_perl(Pargs, char *); void perl_done(Pargs); char *filter_perl(Pargs, char *); #endif #define RNEWS_START "#! rnews" /* string that denotes the begin of a new message in rnews file */ #define RNEWS_START_LEN 8 #define TEMP_ARTICLE "tmp-article" /* name for temp article in rnews_path */ /* stuff needed for language phrases */ /* set up defaults */ char **rpost_phrases = default_rpost_phrases; char **both_phrases = default_both_phrases; enum { RETVAL_ERROR = -1, RETVAL_OK, RETVAL_ARTICLE_PROB, RETVAL_NOAUTH, RETVAL_UNEXPECTEDANS}; /*------------------------------------------------*/ int main(int argc, char *argv[], char *env[]) { char *inbuf; int response, retval, loop, fargc, i; struct stat sbuf; char **args, **fargs; Args myargs; /* initialize everything */ retval = RETVAL_OK; fargc = 0; fargs = NULL; myargs.batch = NULL; myargs.prefix =NULL; myargs.status_fptr = stdout; myargs.deleteyn = FALSE; myargs.auth.userid = NULL; myargs.auth.passwd = NULL; myargs.auth.autoauth = FALSE; myargs.auth.use_env = FALSE; myargs.do_modereader = FALSE; myargs.portnr = DEFAULT_NNRP_PORT; myargs.host = getenv("NNTPSERVER"); /* the default */ myargs.phrases = NULL; myargs.debug = FALSE; myargs.rnews_file = NULL; myargs.rnews_path = NULL; myargs.filter_argc = 0; myargs.filter_infilenr = -1; myargs.filter_outfile = NULL; myargs.show_name = FALSE; myargs.ignore_readonly = FALSE; myargs.do_ssl = FALSE; myargs.ssl_struct = NULL; #ifdef PERL_EMBED myargs.perl_int = NULL; #endif /* have to do this next so if set on cmd line, overrides this */ #ifdef N_PHRASES /* in case someone nukes def */ if(stat(N_PHRASES, &sbuf) == 0 && S_ISREG(sbuf.st_mode)) { /* we have a regular phrases file make it the default */ myargs.phrases = N_PHRASES; } #endif /* allow no args, only the hostname, or hostname as first arg */ /* also have to do the file argument checking */ switch(argc) { case 1: break; case 2: /* the fargs == NULL test so only process first file name */ if(argv[1][0] == FILE_CHAR) { if((fargs = build_args(&argv[1][1], &fargc)) != NULL) { retval = scan_args(&myargs, fargc, fargs); } } else { myargs.host = argv[1]; } break; default: for(loop=1;loopstatus_fptr); if(response!=RETVAL_OK) { error_log(ERRLOG_REPORT, rpost_phrases[5], NULL); retval = RETVAL_ERROR; } else { while(fgets(buf, MAXLINLEN, fptr) != NULL) { len=strlen(buf); if(longline == FALSE) { /* only check this if we are at the beginning of a line */ if(buf[0]=='.') { /* Yup, this has to be doubled */ memmove(buf+1,buf,++len); buf[0]='.'; } } if(buf[len-1] == '\n') { /* only do this if we have an EOL in buffer */ if(buf[len-2] != '\r') { /* only add the \r\n if it's not already there. */ strncpy(&buf[len-1],"\r\n",3); } longline = FALSE; } else { longline = TRUE; } sputline(myargs->sockfd, buf, myargs->do_ssl, myargs->ssl_struct); if(myargs->debug == TRUE) { do_debug("ARTICLE: %s", buf); } } /* if the last line didn't have a nl on it, we need to */ /* put one so that the eom is recognized */ if(longline == TRUE) { sputline(myargs->sockfd, "\r\n", myargs->do_ssl, myargs->ssl_struct); } sputline(myargs->sockfd, ".\r\n", myargs->do_ssl, myargs->ssl_struct); if(myargs->debug == TRUE) { do_debug("ARTICLE END\n"); } if((i = sgetline(myargs->sockfd, &inbuf, myargs->do_ssl, myargs->ssl_struct)) < 0) { retval = RETVAL_ERROR; } else { fputs(inbuf, myargs->status_fptr); if(myargs->debug == TRUE) { do_debug("RESPONSE: %s", inbuf); } ptr = number(inbuf, &response); } if(retval == RETVAL_OK && response!=240) { dupeyn = FALSE; if(response == 441) { number(ptr, &response); } if(response == 435) { dupeyn = TRUE; } else { /* M$ server sends "441 (615) Article Rejected -- Duplicate Message ID" handle that */ /* can't just call nr, cause of parens */ /* ptr should be at ( after 441 */ if( *ptr == '(' ) { number(++ptr, &response); if(response == 615) { dupeyn = TRUE; } } else if (strstr(inbuf, RPOST_DUPE_STR) != NULL || strstr(inbuf, RPOST_DUPE_STR2)) { dupeyn = TRUE; } } if(dupeyn == TRUE) { print_phrases(myargs->status_fptr, rpost_phrases[6], NULL); } else { error_log(ERRLOG_REPORT, rpost_phrases[7], inbuf, NULL); retval = RETVAL_ARTICLE_PROB; } } } return retval; } /*----------------------------------------------------------------*/ int send_command(Pargs myargs, const char *cmd, char **ret_response, int good_response) { /* this is needed so can do user authorization */ int len, retval = RETVAL_OK, nr; char *resp; if(myargs->debug == TRUE) { do_debug("sending command: %s", cmd); } sputline(myargs->sockfd, cmd, myargs->do_ssl, myargs->ssl_struct); len = sgetline(myargs->sockfd, &resp, myargs->do_ssl, myargs->ssl_struct); if( len < 0) { retval = RETVAL_ERROR; } else { if(myargs->debug == TRUE) { do_debug("got answer: %s", resp); } number(resp, &nr); if(nr == 480 ) { /* we must do authorization */ retval = do_authenticate(myargs); if(retval == RETVAL_OK) { /* resend command */ sputline(myargs->sockfd, cmd, myargs->do_ssl, myargs->ssl_struct); if(myargs->debug == TRUE) { do_debug("sending command: %s", cmd); } len = sgetline(myargs->sockfd, &resp, myargs->do_ssl, myargs->ssl_struct); if( len < 0) { retval = RETVAL_ERROR; } else { number(resp,&nr); if(myargs->debug == TRUE) { do_debug("got answer: %s", resp); } } } } if (good_response != 0 && nr != good_response) { error_log(ERRLOG_REPORT, rpost_phrases[18],cmd,resp,NULL); retval = RETVAL_UNEXPECTEDANS; } } if(ret_response != NULL) { *ret_response = resp; } return retval; } /*----------------------------------*/ /* authenticate when receive a 480 */ /*----------------------------------*/ int do_authenticate(Pargs myargs) { int len, nr, retval = RETVAL_OK; char *resp, buf[MAXLINLEN]; /* we must do authorization */ if((myargs->auth).userid == NULL || (myargs->auth).passwd == NULL) { error_log(ERRLOG_REPORT, rpost_phrases[43], NULL); retval = RETVAL_NOAUTH; } else { print_phrases(myargs->status_fptr, rpost_phrases[41], NULL); sprintf(buf, "AUTHINFO USER %s\r\n", (myargs->auth).userid); if(myargs->debug == TRUE) { do_debug("sending command: %s", buf); } sputline(myargs->sockfd, buf, myargs->do_ssl, myargs->ssl_struct); len = sgetline(myargs->sockfd, &resp, myargs->do_ssl, myargs->ssl_struct); if( len < 0) { retval = RETVAL_ERROR; } else { if(myargs->debug == TRUE) { do_debug("got answer: %s", resp); } number(resp, &nr); if(nr == 480) { /* this is because of the pipelining code */ /* we get the second need auth */ /* just ignore it and get the next line */ /* which should be the 381 we need */ if((len = sgetline(myargs->sockfd, &resp, myargs->do_ssl, myargs->ssl_struct)) < 0) { retval = RETVAL_ERROR; } else { number(resp, &nr); } } if(retval == RETVAL_OK) { if(nr != 381) { error_log(ERRLOG_REPORT, rpost_phrases[16], resp, NULL); retval = RETVAL_NOAUTH; } else { sprintf(buf, "AUTHINFO PASS %s\r\n", (myargs->auth).passwd); sputline(myargs->sockfd, buf, myargs->do_ssl, myargs->ssl_struct); if(myargs->debug == TRUE) { do_debug("sending command: %s", buf); } len = sgetline(myargs->sockfd, &resp, myargs->do_ssl, myargs->ssl_struct); if(len < 0) { retval = RETVAL_ERROR; } else { if(myargs->debug == TRUE) { do_debug("got answer: %s", resp); } number(resp, &nr); switch(nr) { case 281: /* bingo */ retval = RETVAL_OK; print_phrases(myargs->status_fptr, rpost_phrases[42], NULL); break; case 502: /* permission denied */ retval = RETVAL_NOAUTH; error_log(ERRLOG_REPORT, rpost_phrases[17], NULL); break; default: /* wacko error */ error_log(ERRLOG_REPORT, rpost_phrases[16], resp, NULL); retval = RETVAL_NOAUTH; break; } } } } } } return retval; } /*--------------------------------------------------------------------------------------*/ int scan_args(Pargs myargs, int argc, char *argv[]) { int loop, retval = RETVAL_OK; for(loop=0;loopdebug == TRUE) { do_debug("Checking arg #%d-%d: '%s'\n", loop, argc, argv[loop]); } /* check for valid args format */ if(argv[loop][0] != '-' && argv[loop][0] != FILE_CHAR) { retval = RETVAL_ERROR; } if(argv[loop][0] == '-' && argv[loop][2] != '\0') { retval = RETVAL_ERROR; } if(retval != RETVAL_OK) { error_log(ERRLOG_REPORT, rpost_phrases[19], argv[loop], NULL); } else if(argv[loop][0] != FILE_CHAR) { switch(argv[loop][1]) { case 'h': /* hostname */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, rpost_phrases[20], NULL); retval = RETVAL_ERROR; } else { myargs->host = argv[++loop]; } break; case 'b': /* batch file Mode, next arg = batch file */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, rpost_phrases[21], NULL); retval = RETVAL_ERROR; } else { myargs->batch = argv[++loop]; } break; case 'p': /* specify directory prefix for files in batch file */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, rpost_phrases[22], NULL); retval = RETVAL_ERROR; } else { myargs->prefix = argv[++loop]; } break; case 'f': /* filter prg, rest of cmd line is args */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, rpost_phrases[23], NULL); retval = RETVAL_ERROR; } else { /* set up filter args send it first arg to filter and arg count */ retval = parse_filter_args(myargs, argc - (loop+1), &(argv[loop+1])); loop = argc; /* terminate main loop */ } break; #ifdef PERL_EMBED case 'F': /* filter using embedded perl */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, rpost_phrases[33], NULL); } else { /* parse the perl filter */ parse_perl(myargs, argv[++loop]); } break; #endif case 'e': /* use default error log path */ error_log(ERRLOG_SET_FILE, ERROR_LOG,NULL); break; case 'E': /* error log path */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, "%s\n",rpost_phrases[24],NULL); retval = RETVAL_ERROR; } else { error_log(ERRLOG_SET_FILE, argv[++loop], NULL); } break; case 's': /* use default status log path */ if((myargs->status_fptr = fopen(STATUS_LOG, "a")) == NULL) { MyPerror(rpost_phrases[25]); retval = RETVAL_ERROR; } break; case 'S': /* status log path */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, rpost_phrases[26],NULL); retval = RETVAL_ERROR; } else if((myargs->status_fptr = fopen(argv[++loop], "a")) == NULL) { MyPerror(rpost_phrases[25]); retval = RETVAL_ERROR; } break; case 'd': /* delete batch file on success */ myargs->deleteyn = TRUE; break; case 'u': /* do auto-authenticate */ myargs->auth.autoauth = TRUE; break; case 'U': /* next arg is userid for authorization */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, rpost_phrases[27],NULL); retval = RETVAL_ERROR; } else { myargs->auth.userid = argv[++loop]; } break; case 'P': /* next arg is password for authorization */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, rpost_phrases[28],NULL); retval = RETVAL_ERROR; } else { myargs->auth.passwd = argv[++loop]; } break; case 'Q': /* get userid/passwd from env */ myargs->auth.use_env = TRUE; myargs->auth.passwd = getenv("NNTP_PASS"); myargs->auth.userid = getenv("NNTP_USER"); break; case 'M': /* do mode reader command */ myargs->do_modereader = TRUE; break; case 'N': /* override port number */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, rpost_phrases[29],NULL); retval = RETVAL_ERROR; } else { myargs->portnr = atoi(argv[++loop]); } break; case 'l': /* language phrase file */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, rpost_phrases[31],NULL); retval = RETVAL_ERROR; } else { myargs->phrases = argv[++loop]; } break; case 'D': /* debug */ myargs->debug = TRUE; /* so error_log goes to debug too */ error_log(ERRLOG_SET_DEBUG, NULL, NULL); break; case 'r': /* do rnews next args base name then path for the rnews files */ if(loop+2 >= argc) { error_log(ERRLOG_REPORT, rpost_phrases[38], NULL); retval = RETVAL_ERROR; } else { myargs->rnews_file = argv[++loop]; myargs->rnews_path = argv[++loop]; } break; case 'n': /* show the file name as we're uploading it */ myargs->show_name = TRUE; break; case 'i': /* ignore read_only opening announcement */ myargs->ignore_readonly = TRUE; break; #ifdef TIMEOUT case 'T': /* timeout */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, rpost_phrases[31], NULL); retval = RETVAL_ERROR; } else { TimeOut = atoi(argv[++loop]); } break; #endif #ifdef HAVE_LIBSSL case 'z': /* use SSL */ myargs->do_ssl = TRUE; myargs->portnr = DEFAULT_SSL_PORT; break; #endif default: error_log(ERRLOG_REPORT, rpost_phrases[30], argv[loop],NULL); break; } } } return retval; } #ifdef PERL_EMBED /*-----------------------------------------------------------------------------------------*/ void parse_perl(Pargs args, char *fname) { /* this code is copied from killprg.c killperl_setup() */ /* which comes from PERLEMBED man page */ char *ptr = NULL; char *prgs[] = { ptr, ptr }; if ((args->perl_int = perl_alloc()) == NULL) { error_log(ERRLOG_REPORT, rpost_phrases[34], NULL); } else { if(args->debug == TRUE) { do_debug("Perl program name =%s\n", fname); } perl_construct(args->perl_int); prgs[1] = fname; if(perl_parse(args->perl_int, NULL, 2, prgs, NULL) == 0) { perl_run(args->perl_int); } else { error_log(ERRLOG_REPORT, rpost_phrases[35], fname, NULL); perl_done(args); } } } /*---------------------------------------------------------------------------------------*/ void perl_done(Pargs args) { /* all done, free everything up */ if(args->perl_int != NULL) { perl_destruct(args->perl_int); perl_free(args->perl_int); args->perl_int = NULL; } } /*--------------------------------------------------------------------------------------*/ char *filter_perl(Pargs myargs, char *file) { int i; char *args[2] ={ NULL, NULL}; char infilen[MAXLINLEN+1]; char *infile=NULL; dSP; /* perl stack pointer */ SV *fname; /* used to get fname off of perl stack */ /* here's where we send it to perl and get back our filename */ /* we need infile to become our file name to post*/ /* this code comes from chk_msg_kill_perl() which uses */ /* both perlcall and perlembed man pages */ /* set up args */ args[0] = file; if(myargs->debug == TRUE) { do_debug("Calling %s with arg %s\n", PERL_RPOST_SUB, file); } ENTER; SAVETMPS; PUSHMARK(SP); i = perl_call_argv(PERL_RPOST_SUB, G_SCALAR | G_EVAL | G_KEEPERR, args); SPAGAIN; if(SvTRUE(ERRSV)) { error_log(ERRLOG_REPORT, rpost_phrases[36], SvPV(ERRSV,PL_na)); POPs; perl_done(myargs); } else if(i != 1) { error_log(ERRLOG_REPORT, rpost_phrases[37], PERL_RPOST_SUB, NULL); perl_done(myargs); } else { fname = POPs; infile = SvPV(fname, PL_na); if(myargs->debug == TRUE) { do_debug("got string =%s\n", null_str(infile)); } /* have to do the following cause as soon as we do FREETMPS, the */ /* data that infile points to will disappear */ strcpy(infilen, infile); infile = infilen; /* so we return the name of the file */ } PUTBACK; FREETMPS; LEAVE; return infile; } #endif /*---------------------------------------------------------------------------------------*/ int parse_filter_args(Pargs myargs, int argcount, char **args) { int argon, loop, i, retval = RETVAL_OK; /* build filter args */ i = strlen(RPOST_FILTER_OUT); /* just in case */ myargs->filter_outfile = NULL; myargs->filter_infilenr = -1; /* build array of args to past to fork, execl */ /* if see RPOST_FILTER_IN as arg, substitute the infile name */ /* if see RPOST_FILTER_OUT as first part of arg, get outfile name */ for(argon = 0, loop = 0; loop < argcount; loop++) { if(myargs->debug == TRUE) { do_debug("loop=%d argon=%d arg=%s\n", loop, argon, args[loop]); } if(strcmp(args[loop], RPOST_FILTER_IN) == 0) { /* substitute infile name */ myargs->filter_args[argon] = NULL; /* just so debug.suck looks okay */ myargs->filter_infilenr = argon++; } else if(strncmp(args[loop], RPOST_FILTER_OUT, (unsigned int) i) == 0) { /* arg should be RPOST_FILTER_OUT=filename */ if(args[loop][i] != '=') { error_log(ERRLOG_REPORT, rpost_phrases[17], args[loop], NULL); } else { myargs->filter_outfile = (char *)&(args[loop][i+1]); } } else { myargs->filter_args[argon++] = args[loop]; } } myargs->filter_argc = argon; /* the count of arguments */ myargs->filter_args[argon] = NULL; /* just to be on the safe side */ if(myargs->filter_outfile == NULL) { /* no outfile defined, use built-in default */ myargs->filter_outfile = tmpnam(NULL); error_log(ERRLOG_REPORT, rpost_phrases[9], myargs->filter_outfile, NULL); } if(myargs->filter_infilenr < 0) { error_log(ERRLOG_REPORT, rpost_phrases[10], NULL); retval = RETVAL_ERROR; } return retval; } /*--------------------------------------------------------------------*/ char *do_filter(Pargs myargs, char *filename) { char *infile = NULL; int i; myargs->filter_args[myargs->filter_infilenr] = filename; /* so this points to the right name */ if( myargs->debug == TRUE) { do_debug("ARGS:"); for(i=0;ifilter_argc;i++) { do_debug(" %d=%s", i, myargs->filter_args[i]); } do_debug("\n"); } switch((int) fork()) { case 0: /* in child, do execl */ if(execvp(myargs->filter_args[0], myargs->filter_args) == -1) { MyPerror(rpost_phrases[14]); exit(-1); /* so we don't get two running */ } break; case -1: /* should never get here */ MyPerror(rpost_phrases[15]); break; default: /* in parent, wait for child to finish */ wait(NULL); /* no status check on finish */ infile = myargs->filter_outfile; } return infile; } /*----------------------------------------------------------------------------------*/ int filter_post_article(Pargs myargs, char *filename) { char *infile = NULL; /* this is the filename that gets passed to do_article() */ int retval = RETVAL_OK; FILE *fpi_msg; #ifdef PERL_EMBED if(myargs->perl_int != NULL) { infile = filter_perl(myargs, filename); } else #endif if(myargs->filter_argc >0) { infile = do_filter(myargs, filename); } else { /* so we open up the right file */ infile = filename; } if(infile == NULL) { error_log(ERRLOG_REPORT, rpost_phrases[11], NULL); retval = RETVAL_ERROR; } else { if(myargs->show_name == TRUE) { /* show the file name */ print_phrases(myargs->status_fptr, rpost_phrases[40], infile, NULL); } if((fpi_msg = fopen(infile, "r")) == NULL) { /* skip to next article if infile don't exist */ error_log(ERRLOG_REPORT, rpost_phrases[12],NULL); } else { retval = do_article(myargs, fpi_msg); if(myargs->debug == TRUE && retval != RETVAL_OK) { do_debug("do_article() returned %d\n", retval); } fclose(fpi_msg); } } return retval; } /*--------------------------------------------------------------------------------*/ int do_batch(Pargs myargs) { int i, x, nrdone, nrok, retval; FILE *fpi_batch; char buf[MAXLINLEN+1], file[MAXLINLEN+1]; char *outfile; outfile = NULL; retval = RETVAL_OK; nrdone = nrok = 0; if((fpi_batch = fopen(myargs->batch, "r")) == NULL) { MyPerror(myargs->batch); retval = RETVAL_ERROR; } else { while((retval != RETVAL_ERROR) && (fgets(buf, MAXLINLEN, fpi_batch) != NULL)) { /* build file name */ /* if no prefix, or if this is a inn token (not a real file), just copy it */ if(myargs->prefix == NULL || (file[0] == '@' && file[strlen(file)-1] == '@')) { strcpy(file, buf); } else { strcpy(file, myargs->prefix); if(file[strlen(file)-1] != '/') { strcat(file, "/"); } strcat(file, buf); } /* strip off nl */ i = strlen(file); if(file[i-1] == '\n') { file[i-1] = '\0'; } /* some INNs put article number on line in addition to file name */ /* so lets parse through string, if find a ' ' NULL terminate at */ /* that point */ for(x=0;xdebug == TRUE) { do_debug("Article Name: %s\n", file); } retval = filter_post_article(myargs, file); nrdone++; if(retval == RETVAL_OK) { nrok++; } /* log failed uploads (dupe article is not a failed upload ) */ else if (retval == RETVAL_ARTICLE_PROB) { log_fail(myargs->batch, buf); } } if(retval == RETVAL_ERROR) { /* write out the rest of the batch file to the failed file */ do { log_fail(myargs->batch, buf); } while (fgets(buf, MAXLINLEN, fpi_batch) != NULL); } fclose(fpi_batch); if(retval != RETVAL_ERROR) { retval = (nrok == nrdone) ? RETVAL_OK : RETVAL_ARTICLE_PROB; } if(myargs->deleteyn == TRUE && retval == RETVAL_OK) { unlink(myargs->batch); print_phrases(myargs->status_fptr, rpost_phrases[13], myargs->batch,NULL); } } return retval; } /*---------------------------------------------------------------*/ void log_fail(char *batch_file, char *article) { char file[MAXLINLEN+1]; FILE *fptr; sprintf(file, "%s%s", batch_file, RPOST_FAIL_EXT); if((fptr = fopen(file, "a")) == NULL) { MyPerror(file); } else { fputs(article, fptr); fclose(fptr); } } /*------------------------------------------------------------------------------------------------*/ int do_rnews(Pargs myargs) { /* handle rnews files */ int len, nrdone, nrok, retval = RETVAL_OK; DIR *dptr; FILE *f_rnews, *f_temp; char rnews_path[PATH_MAX+1], temp_path[PATH_MAX+1], linein[MAXLINLEN]; struct dirent *dentry; sprintf(temp_path, "%s/%s", myargs->rnews_path, TEMP_ARTICLE); /* only need to do this once */ if(myargs->rnews_file == NULL || myargs->rnews_path == NULL) { error_log(ERRLOG_REPORT, rpost_phrases[39], NULL); retval = RETVAL_ERROR; } else if((dptr = opendir(myargs->rnews_path)) == NULL) { error_log(ERRLOG_REPORT, rpost_phrases[39], NULL); retval = RETVAL_ERROR; } else { if(myargs->debug == TRUE) { do_debug("Reading directory %s\n", myargs->rnews_path); } len = strlen(myargs->rnews_file); /* work our way thru the directory find our files that */ /* start with our rnews_file prefix */ while((dentry = readdir(dptr)) != NULL && retval != RETVAL_ERROR) { if(strncmp(dentry->d_name, myargs->rnews_file, len) == 0) { /* bingo, we've found one of the files*/ /* now work our way thru it and create our temp article */ /* which can be fed thru the filters and then to do_article() */ nrdone = nrok = 0; /* which article are we on */ sprintf(rnews_path, "%s/%s", myargs->rnews_path, dentry->d_name); if(myargs->debug == TRUE) { do_debug("Trying to read rnews file %s\n", rnews_path); } if((f_rnews = fopen(rnews_path, "r")) == NULL) { MyPerror(rnews_path); retval = RETVAL_ERROR; } else { f_temp = NULL; /* now work our way thru the file */ while(retval != RETVAL_ERROR && fgets(linein, MAXLINLEN, f_rnews) != NULL) { if(strncmp(linein, RNEWS_START, RNEWS_START_LEN) == 0) { if(myargs->debug == TRUE) { do_debug("Found rnews line %s", linein); } if(f_temp != NULL) { /* close out the old file, and process it */ fclose(f_temp); nrdone++; retval = filter_post_article(myargs, temp_path); if(retval == RETVAL_OK) { nrok++; } } if((f_temp = fopen(temp_path, "w")) == NULL) { MyPerror(temp_path); retval = RETVAL_ERROR; } } else if(f_temp != NULL) { /* write the line out to the file */ fputs(linein, f_temp); } } if(f_temp != NULL) { /* close out our final article */ fclose(f_temp); nrdone++; retval = filter_post_article(myargs, temp_path); if(retval == RETVAL_OK) { nrok++; } } fclose(f_rnews); if(retval != RETVAL_ERROR) { retval = (nrok == nrdone) ? RETVAL_OK : RETVAL_ARTICLE_PROB; } if(myargs->deleteyn == TRUE && retval == RETVAL_OK) { unlink(rnews_path); print_phrases(myargs->status_fptr, rpost_phrases[13], rnews_path,NULL); } } } } closedir(dptr); } return retval; } /*--------------------------------------------------------------------------------*/ /* THE strings in this routine is the only one not in the arrays, since */ /* we are in the middle of reading the arrays, and they may or may not be valid. */ /*--------------------------------------------------------------------------------*/ void load_phrases(Pargs myargs) { int error=TRUE; FILE *fpi; char buf[MAXLINLEN]; if(myargs->phrases != NULL) { if((fpi = fopen(myargs->phrases, "r")) == NULL) { MyPerror(myargs->phrases); } else { fgets(buf, MAXLINLEN, fpi); if(strncmp( buf, SUCK_VERSION, strlen(SUCK_VERSION)) != 0) { error_log(ERRLOG_REPORT, "Invalid Phrase File, wrong version\n", NULL); } else if((both_phrases = read_array(fpi, NR_BOTH_PHRASES, TRUE)) != NULL && (rpost_phrases = read_array(fpi, NR_RPOST_PHRASES, TRUE)) != NULL) { error = FALSE; } } fclose(fpi); if(error == TRUE) { /* reset back to default */ error_log(ERRLOG_REPORT, "Using default Language phrases\n",NULL); rpost_phrases = default_rpost_phrases; both_phrases = default_both_phrases; } } } /*--------------------------------------------------------------------------------*/ void free_phrases(void) { /* free up the memory alloced in load_phrases() */ if(rpost_phrases != default_rpost_phrases) { free_array(NR_RPOST_PHRASES, rpost_phrases); } if(both_phrases != default_both_phrases) { free_array(NR_BOTH_PHRASES, both_phrases); } } suck-4.3.4/rpost_phrases.c000066400000000000000000000036431333033562000155330ustar00rootroot00000000000000/* the default phrases */ const char *default_rpost_phrases[] = { "Usage: %v1% hostname [@filename] [-s | -S name] [-e | -E name] [-b batch_file -p dir_prefix]", /* 0 */ "-U userid -P passwd] [-M] [-N portnr] [-T timeout] [-u] [-f filter_args] \n", "\tIf used, filter_args MUST be last arguments on line\n", "Sorry, Can't post to this host\n", "Closing connection to %v1%\n", "Bad luck. You can't use this server.\n", /* 5 */ "Duplicate article, unable to post\n", "Malfunction, Unable to post Article!\n%v1%", "Invalid argument: %v1%\n", "Using Built-In default %v1%\n", "No infile specification, aborting\n", /* 10 */ "No file to process, aborting\n", "Empty file, skipping\n", "Deleting batch file: %v1%\n", "Execl", "Fork", /* 15 */ "Weird Response to Authorization: %v1%\n", "Authorization Denied", "*** Unexpected response to command: %v1%\n%v2%\n", "Invalid argument: %v1%\n", "No Host Name Specified\n", /* 20 */ "No Batch file Specified\n", "No prefix Supplied\n", "No args to use for filter\n", "No Error Log name provided\n", "rpost: Status Log", /* 25 */ "No Status Log name provided\n", "No Userid Specified\n", "No Password Specified\n", "No NNRP port Specified\n", "Invalid argument: %v1%, ignoring\n", /* 30 */ "No language file specified\n", "No userid or password provided, unable to auto-authenticate", "No Perl script supplied, ignoring\n", "Perl set up- out of memory, ignoring\n", "Perl - error parse file %v1%, ignoring\n", /* 35 */ "Perl - evaluation error %v1%, aborting\n", "Perl - invalid nr of return values, %v1%, aborting\n", "No rnews file name or rnews path provided\n", "Rnews file or path is invalid\n", "Uploading file-> %v1%\n", /*40*/ "Attempting to authenticate user\n", "Authentication succeeded\n", "No userid or password provided, unable to authenticate\n", }; int nr_rpost_phrases = sizeof(default_rpost_phrases)/sizeof(default_rpost_phrases[0]); suck-4.3.4/sample/000077500000000000000000000000001333033562000137465ustar00rootroot00000000000000suck-4.3.4/sample/get.news.generic000077500000000000000000000046351333033562000170510ustar00rootroot00000000000000#!/bin/sh #BEFORE USING - check to ensure all the paths defined below are correct!! #NOTE: this script probably needs to be run by root. Most systems will # not let a normal user run rnews REMOTE_HOST=news.pixi.com LOCAL_HOST=localhost SPOOLDIR=/usr/spool/news # base directory for articles to be rposted NEWSDIR=/usr/lib/news # base directory for news binaries BASEDIR=/home/boby/doNews # base directory for suck rpost and scripts SHLOCK=${NEWSDIR}/bin/shlock TMPDIR=${BASEDIR} # location for suck.* files MSGDIR=${BASEDIR}/Msgs # where to put MultiFile articles when getting them OUTGOING=${SPOOLDIR}/out.going/pixi # location of the list of articles to upload SCRIPT=${BASEDIR}/put.news # my filter for rpost OUTFILE=/tmp/tmp$$ # used by rpost as article after it is filtered LOCKFILE=${BASEDIR}/getnews.lock # lock file to prevent multiple instances of script TESTHOST=testhost RPOST=rpost SUCK=suck TESTHOST=testhost # if we are already running, abort trap 'rm -f ${LOCKFILE} ; echo "Aborting" ; exit 1' 1 2 3 15 ${SHLOCK} -p $$ -f ${LOCKFILE} if [ $? -ne 0 ]; then echo "Already running, can't run two at one time" exit fi # is the local host up and running so we can post articles we download? ${TESTHOST} ${LOCAL_HOST} -s LOCAL_RESULT=$? # is the remote host up and running so we can download articles? ${TESTHOST} ${REMOTE_HOST} -s REMOTE_RESULT=$? if [ ${REMOTE_RESULT} -eq 0 -a ${LOCAL_RESULT} -eq 0 ]; then # download articles ${SUCK} ${REMOTE_HOST} -c -A -bp -hl ${LOCAL_HOST} -dt ${TMPDIR} -dm ${MSGDIR} -dd ${BASEDIR} SUCK_STATUS=$? if [ ${SUCK_STATUS} -eq 0 ]; then echo "Downloaded Articles" elif [ ${SUCK_STATUS} -eq 1 ]; then echo "No articles to download" elif [ ${SUCK_STATUS} -eq 2 ]; then echo "Unexpected answer from remote server to an issued command" elif [ ${SUCK_STATUS} -eq 4 ]; then echo "Can't do NNTP authorization" elif [ ${SUCK_STATUS} -eq -1 ]; then echo "General failure" fi # now upload articles if [ -s ${OUTGOING} ]; then # outgoing articles to post ${TESTHOST} ${REMOTE_HOST} -s if [ $? -ne 0 ]; then echo "Remote posting host not responding" else ${RPOST} ${REMOTE_HOST} -d -b ${OUTGOING} -p ${SPOOLDIR} -f \$\$o=${OUTFILE} ${SCRIPT} \$\$i ${OUTFILE} if [ $? -ne 0 ]; then echo "Error remote posting" else echo "Remotely posted articles" rm ${OUTFILE} fi fi fi echo "You can hang up the modem now" fi rm -f ${LOCKFILE} suck-4.3.4/sample/get.news.inn000077500000000000000000000075711333033562000162230ustar00rootroot00000000000000#!/bin/sh #BEFORE USING - check to ensure all the paths defined below are correct!! #NOTE: this script probably needs to be run by root. Most systems will # not let a normal user run ctlinnd REMOTE_HOST=news.pixi.com LOCAL_HOST=localhost SPOOLDIR=/usr/spool/news # base directory for articles to be rposted NEWSDIR=/usr/lib/news # base directory for news binaries BASEDIR=/home/boby/doNews # base directory for scripts and data files CTLINND=${NEWSDIR}/bin/ctlinnd # location of binary SHLOCK=${NEWSDIR}/bin/shlock # location of binary TMPDIR=${BASEDIR} # location for suck.* files MSGDIR=${BASEDIR}/Msgs # where to put MultiFile messages when getting them SITE=pixi # name of site from newsfeeds file OUTGOING=${SPOOLDIR}/out.going/${SITE} # location of the list of articles to upload OUTGOINGNEW=${OUTGOING}.new # file to contain the list temporarily OUTGOINGFAIL=${OUTGOINGNEW}.fail # file with failed xfers SCRIPT=${BASEDIR}/put.news # my filter for rpost OUTFILE=/tmp/tmp$$ # used by rpost as article after it is filtered LOCKFILE=${BASEDIR}/getnews.lock # lock file to prevent multiple instances of script NEWSGROUP=news # which group owns the file in out.going, typically either news or uucp. TESTHOST=testhost RPOST=rpost SUCK=suck # if we are already running, abort trap 'rm -f ${LOCKFILE} ; echo "Aborting" ; exit 1' 1 2 3 15 ${SHLOCK} -p $$ -f ${LOCKFILE} if [ $? -ne 0 ]; then echo "Already running, can't run two at one time" exit fi # is the local host up and running so we can post messages we download? ${TESTHOST} ${LOCAL_HOST} -s LOCAL_RESULT=$? # is the remote host up and running so we can download messages? ${TESTHOST} ${REMOTE_HOST} -s REMOTE_RESULT=$? if [ ${REMOTE_RESULT} -eq 0 -a ${LOCAL_RESULT} -eq 0 ]; then # download messages ${SUCK} ${REMOTE_HOST} -c -A -bp -hl ${LOCAL_HOST} -dt ${TMPDIR} -dm ${MSGDIR} -dd ${BASEDIR} SUCK_STATUS=$? if [ ${SUCK_STATUS} -eq 0 ]; then echo "Downloaded Articles" elif [ ${SUCK_STATUS} -eq 1 ]; then echo "No articles to download" elif [ ${SUCK_STATUS} -eq 2 ]; then echo "Unexpected answer from remote server to an issued command" elif [ ${SUCK_STATUS} -eq 4 ]; then echo "Can't do NNTP authorization" elif [ ${SUCK_STATUS} -eq -1 ]; then echo "General failure" fi # now upload messages if [ -s ${OUTGOING} -o -s ${OUTGOINGNEW} ]; then ${TESTHOST} ${REMOTE_HOST} -s if [ $? -ne 0 ]; then echo "Remote posting host not responding" else # this is needed by INND so that the outgoing file will be # properly flushed and we have a new blank file to work with # when we are done # First mv the current one to a new file name # Since innd already has the file open, it doesn't care # about the rename. # The flush will ensure that all messages to be posted have # been written out, close off the old one (already renamed) # and create a new one. # if the outgoingnew already exists, it means we aborted last time # so don't try to do it again if [ ! -s ${OUTGOINGNEW} ]; then mv ${OUTGOING} ${OUTGOINGNEW} ${CTLINND} flush ${SITE} fi # outgoing messages to post ${RPOST} ${REMOTE_HOST} -d -b ${OUTGOINGNEW} -p ${SPOOLDIR} -f \$\$o=${OUTFILE} ${SCRIPT} \$\$i ${OUTFILE} ERRLEV=$? if [ ${ERRLEV} -eq 0 ]; then echo "Remotely posted articles" rm ${OUTFILE} elif [ ${ERRLEV} -eq 1 ]; then echo "Error posting a message" elif [ ${ERRLEV} -eq 2 ]; then echo "Unable to do NNTP authorization with remote server" elif [ ${ERRLEV} -eq 3 ]; then echo "Unexpected answer from remote server to a command while doing NNTP authorization" elif [ ${ERRLEV} -eq -1 ]; then echo "Fatal error" fi if [ -f ${OUTGOINGFAIL} ]; then mv ${OUTGOINGFAIL} ${OUTGOINGNEW} # so we can re do it chown news.${NEWSGROUP} ${OUTGOINGNEW} chmod 664 ${OUTGOINGNEW} fi fi fi echo "You can hang up the modem now" fi rm -f ${LOCKFILE} suck-4.3.4/sample/killprg_child.c000066400000000000000000000021341333033562000167210ustar00rootroot00000000000000#include #include #include #include #define LENGTHLEN 8 #define DEBUG 1 #ifdef DEBUG #define N_DEBUG "debug.child" void do_debug(const char *fmt, ...); #endif void main(int argc, char *argv[]) { char buf[4096]; int len, i, nron; nron = 0; do { i = read(0, buf, LENGTHLEN); buf[LENGTHLEN] = '\0'; #ifdef DEBUG do_debug("Got %d bytes '%s'\n", i, buf); #endif len = atoi(buf); if(len>0) { nron++; i = read(0, buf, len); #ifdef DEBUG do_debug("Got %d bytes\n", i); #endif if((nron % 2) == 0) { i = write(1, "0\n", 2); #ifdef DEBUG do_debug("Wrote %d bytes\n", i); #endif } else { i = write(1, "1\n", 2); #ifdef DEBUG do_debug("Wrote %d bytes\n", i); #endif } } } while (len > 0); } #ifdef DEBUG /*-------------------------------------------------------------*/ void do_debug(const char *fmt, ...) { FILE *fptr = NULL; va_list args; if((fptr = fopen(N_DEBUG, "a")) == NULL) { fptr = stderr; } va_start(args, fmt); vfprintf(fptr, fmt, args); va_end(args); if(fptr != stderr) { fclose(fptr); } } #endif suck-4.3.4/sample/killxover_child.c000066400000000000000000000025761333033562000173060ustar00rootroot00000000000000#include #include #include #include #define LENGTHLEN 8 #define DEBUG 1 #ifdef DEBUG #define N_DEBUG "debug.child" void do_debug(const char *fmt, ...); #endif int main(int argc, char *argv[]) { char buf[4096]; int len, i, nron; /* first have to get the overview.fmt */ i = read(0, buf, LENGTHLEN); buf[LENGTHLEN] = '\0'; #ifdef DEBUG do_debug("Got %d bytes '%s'\n", i, buf); #endif len = atoi(buf); if(len>0) { i = read(0, buf, len); } #ifdef DEBUG do_debug("Got %d bytes '%s'\n", i, buf); #endif nron = 0; do { i = read(0, buf, LENGTHLEN); buf[LENGTHLEN] = '\0'; #ifdef DEBUG do_debug("Got %d bytes '%s'\n", i, buf); #endif len = atoi(buf); if(len>0) { nron++; i = read(0, buf, len); #ifdef DEBUG do_debug("Got %d bytes\n", i); #endif if((nron % 2) == 0) { i = write(1, "0\n", 2); #ifdef DEBUG do_debug("Wrote %d bytes\n", i); #endif } else { i = write(1, "1\n", 2); #ifdef DEBUG do_debug("Wrote %d bytes\n", i); #endif } } } while (len > 0); return 0; } #ifdef DEBUG /*-------------------------------------------------------------*/ void do_debug(const char *fmt, ...) { FILE *fptr = NULL; va_list args; if((fptr = fopen(N_DEBUG, "a")) == NULL) { fptr = stderr; } va_start(args, fmt); vfprintf(fptr, fmt, args); va_end(args); if(fptr != stderr) { fclose(fptr); } } #endif suck-4.3.4/sample/perl_kill.pl000077500000000000000000000003031333033562000162570ustar00rootroot00000000000000# sample perl routine for killfiles package Embed::Persistant; sub perl_kill { my $header = $_[0]; # header my $retval = 0; if($header =~ /WANTED/) { $retval = 1; } return $retval; } suck-4.3.4/sample/perl_xover.pl000077500000000000000000000005721333033562000164770ustar00rootroot00000000000000# sample perl routine for killfiles package Embed::Persistant; sub perl_overview { my $overview = $_[0]; do_debug("got $overview"); } sub perl_xover { my $header = $_[0]; # header my $retval = 0; if($header =~ /WANTED/) { $retval = 1; } do_debug("got $header"); return $retval; } sub do_debug { open FIP, ">>debug.child"; print FIP @_; close FIP; } suck-4.3.4/sample/post_filter.pl000077500000000000000000000014701333033562000166420ustar00rootroot00000000000000#!/usr/bin/perl # sample program for editting/changing the downloaded articles. # this program is passed the directory where the articles are # stored # it modifies the Path: header, so you can add your own unique # host to it, so you can tell INN not to upload these articles opendir (DIRP, $ARGV[0]) or die "Can't open $ARGV[0]"; # get list of files, skipping hidden files @files = grep ( !/^\./, readdir(DIRP)); foreach $file ( @files) { # read the file in $path = "${ARGV[0]}/${file}"; open FIP, "<${path}" or die "Can't read ${path}\n"; @file = ; close FIP; # find the line and change it foreach $line ( @file) { if ( $line =~ /^Path: /) { $line =~ s/^Path: /Path: myhost\!/ } } # save it back out open FIP, ">${path}" or die "Can't write to ${path}\n"; print FIP @file; close FIP; } suck-4.3.4/sample/put.news000077500000000000000000000011161333033562000154560ustar00rootroot00000000000000#!/bin/sh # this is just a simple script to use sed to strip off the # NNTP_Posting_Host and Xref headers that my ISP's newsfeed # doesn't like. this could be written as a one liner # sed -e SEDCMD1 $1 | sed SEDCMD2 > $2 #set -x if [ $# -ne 2 ]; then echo echo "Usage `basename $0` infile outfile " echo exit 1 fi SEDCMD="/^NNTP-Posting-Host/d" SEDCMD2="/^Xref/d" OUTFILE=$2 INFILE=$1 if [ -f ${INFILE} ]; then sed -e ${SEDCMD} ${INFILE} | sed -e ${SEDCMD2} > ${OUTFILE} if [ $? -ne 0 ]; then echo "Error" exit 1 fi else echo "$1 does not exist" exit 1 fi suck-4.3.4/sample/put.news.pl000077500000000000000000000025461333033562000161000ustar00rootroot00000000000000# sample perl routine for rpost # 3 key things # 1. we get in the input file name # 2. we return the output file name # 3. You MUST have the close on both the input # file and output file. Remember this is # running as a subroutine, not a full blown program # and perl only closes files upon program exit, or # reuse of file handle. # WARNING USING /tmp/tmp.rpost is insecure, # you should change this to somewhere normal # users can't read or write. # package Embed::Persistant; sub perl_rpost { my $infile = $_[0]; # input file name my $outfile = "/tmp/tmp.rpost"; # open files for input and output # if we can't open the file, fail with the filename # rpost will then handle the error # We do this since rpost can't differentiate between # a missing file or a script file open IFP, "<${infile}" or return $infile; # open IFP, "<${infile}" or die "Can't read ${infile}"; open OFP, ">${outfile}" or die "Can't create ${outfile}"; # now strip out what we don't need while ( $line = ) { if($line !~ /^NNTP-Posting|^Xref|^X-Trace|^X-Comp/) { # write out to file print OFP $line; } if($line =~ /^$/) { last; # end of header don't need to test anymore # this test is here so the line gets printed out. } } while ( $line = ) { print OFP $line; } close IFP; close OFP; # return output file name return $outfile; } suck-4.3.4/sample/put.news.sm000077500000000000000000000011631333033562000160760ustar00rootroot00000000000000#!/bin/sh # this is just a simple script to use sed to strip off the # NNTP_Posting_Host and Xref headers that my ISP's newsfeed # doesn't like. # It is set up for INND using CNFS, so it uses the sm command # to get the actual article # # Warning using /tmp/tmp$$ is insecure, you should change # this to somewhere normal users can read or write. TMPFILE=/tmp/tmp$$ SEDCMD="/^NNTP-Posting-Host/d" SEDCMD2="/^Xref/d" OUTFILE=$2 /usr/news/bin/sm $1 > $TMPFILE if [ -f ${TMPFILE} ]; then sed -e ${SEDCMD} ${TMPFILE} | sed -e ${SEDCMD2} > ${OUTFILE} if [ $? -ne 0 ]; then echo "Error" exit 1 fi rm ${TMPFILE} fi suck-4.3.4/sample/put.news.sm.pl000077500000000000000000000026011333033562000165060ustar00rootroot00000000000000# sample perl routine for rpost # 3 key things # 1. we get in the input file name # 2. we return the output file name # 3. You MUST have the close on both the input # file and output file. Remember this is # running as a subroutine, not a full blown program # and perl only closes files upon program exit, or # reuse of file handle. # # Warning use of /tmp/tmp.rpost is insecure # you should change this to somewhere normal # users can't read or write. package Embed::Persistant; sub perl_rpost { my $infile = $_[0]; # article reference in cnfs my $outfile = "/tmp/tmp.rpost"; # open files for input and output # if we can't open the file, fail with the filename # rpost will then handle the error # We do this since rpost can't differentiate between # a missing file or a script file open IFP, "/usr/news/bin/sm $infile |" or return $infile; # open IFP, "<${infile}" or die "Can't read ${infile}"; open OFP, ">${outfile}" or die "Can't create ${outfile}"; # now strip out what we don't need while ( $line = ) { if($line !~ /^NNTP-Posting|^Xref|^X-Trace|^X-Comp/) { # write out to file print OFP $line; } if($line =~ /^$/) { last; # end of header don't need to test anymore # this test is here so the line gets printed out. } } while ( $line = ) { print OFP $line; } close IFP; close OFP; # return output file name return $outfile; } suck-4.3.4/sample/suckkillfile.sample000066400000000000000000000002211333033562000176250ustar00rootroot00000000000000HILINES=10000 LOWLINES=10 PATH=news.pixi.com,localhost Subject:win-95 From:nerd@xxx From:geek@yyy QUOTE=\ GROUP=keep comp.os.linux.announce cola suck-4.3.4/sample/sucknewsrc.sample000066400000000000000000000004041333033562000173360ustar00rootroot00000000000000comp.os.linux.announce 1101 comp.os.linux.answers 0 comp.os.linux.development.apps 6063 comp.os.linux.development.system 8908 comp.os.linux.hardware 14119 comp.os.linux.misc 30399 comp.os.linux.networking 12424 comp.os.linux.setup 17810 #comp.os.linux.x 12287 suck-4.3.4/sample/suckothermsgs.sample000066400000000000000000000000641333033562000200520ustar00rootroot00000000000000 suck-4.3.4/ssort.c000066400000000000000000000062451333033562000140120ustar00rootroot00000000000000#include #ifdef DMALLOC #include #endif #include #include "suck_config.h" #include "suck.h" #include "suckutils.h" #include "ssort.h" /* THREE-WAY RADIX QUICKSORT, faster version */ /* From Dr Dobb's Journal Nov 1999 */ /* modified to work with PList vice strings */ /* prototypes */ void swap(PList *, int, int); void vecswap(PList *, int, int, int); int med3func(PList *, int, int, int, int); void inssort(PList *, int, int); /* Support functions */ #ifndef min #define min(a, b) ((a)<=(b) ? (a) : (b)) #endif /*-------------------------------------------------------------*/ void swap(PList *a, int i, int j) { PList t = a[i]; a[i] = a[j]; a[j] = t; } /*--------------------------------------------------------------*/ void vecswap(PList *a, int i, int j, int n) { while (n-- > 0) { swap(a, i++, j++); } } /*---------------------------------------------------------------*/ int med3func(PList *a, int ia, int ib, int ic, int depth) { int va, vb, vc; if ((va=a[ia]->msgnr[depth]) == (vb=a[ib]->msgnr[depth])) { return ia; } if ((vc=a[ic]->msgnr[depth]) == va || vc == vb) { return ic; } return va < vb ? (vb < vc ? ib : (va < vc ? ic : ia ) ) : (vb > vc ? ib : (va < vc ? ia : ic ) ); } /*-----------------------------------------------------------------*/ void inssort(PList *a, int n, int depth) { int i, j; for (i = 1; i < n; i++) { for (j = i; j > 0; j--) { if (strcmp(&(a[j-1]->msgnr[depth]), &(a[j]->msgnr[depth])) <= 0) { break; } swap(a, j, j-1); } } } /*-----------------------------------------------------------------*/ void ssort(PList *a, int n, int depth) { int le, lt, gt, ge, r, v; int pl, pm, pn, d; if (n <= 10) { inssort(a, n, depth); return; } pl = 0; pm = n/2; pn = n-1; if (n > 50) { d = n/8; pl = med3func(a, pl, pl+d, pl+2*d,depth); pm = med3func(a, pm-d, pm, pm+d,depth); pn = med3func(a, pn-2*d, pn-d, pn,depth); } pm = med3func(a, pl, pm, pn,depth); swap(a, 0, pm); v = a[0]->msgnr[depth]; for (le = 1; le < n && a[le]->msgnr[depth] == v; le++) { ; } if (le == n) { if (v != 0) { ssort(a, n, depth+1); } return; } lt = le; gt = ge = n-1; for (;;) { for ( ; lt <= gt && a[lt]->msgnr[depth] <= v; lt++) { if (a[lt]->msgnr[depth] == v) { swap(a, le++, lt); } } for ( ; lt <= gt && a[gt]->msgnr[depth] >= v; gt--) { if (a[gt]->msgnr[depth] == v) { swap(a, gt, ge--); } } if (lt > gt) { break; } swap(a, lt++, gt--); } r = min(le, lt-le); vecswap(a, 0, lt-r, r); r = min(ge-gt, n-ge-1); vecswap(a, lt, n-r, r); ssort(a, lt-le, depth); if (v != 0) { ssort(a + lt-le, le + n-ge-1, depth+1); } ssort(a + n-(ge-gt), ge-gt, depth); } /*-------------------------------------------------------------------------------*/ PList my_bsearch(PList *arr, char *matchnr, int nrin) { int val, my_index, left = 0, right = nrin ; PList retval = NULL; while( left < right ) { my_index = ( right + left) / 2; /* find halfway pt */ val = qcmp_msgid(matchnr, arr[my_index]->msgnr); if(val < 0) { right = my_index ; } else if(val > 0) { left = my_index + 1; } else { retval = arr[my_index]; break; } } return retval; } suck-4.3.4/ssort.h000066400000000000000000000002231333033562000140050ustar00rootroot00000000000000#ifndef _SUCK_SSORT_H /* function prototypes */ void ssort(PList *, int, int); PList my_bsearch(PList *, char *, int); #endif /* SUCK_SSORT.H */ suck-4.3.4/stamp-h000066400000000000000000000000121333033562000137520ustar00rootroot00000000000000timestamp suck-4.3.4/stamp-h.in000066400000000000000000000000001333033562000143540ustar00rootroot00000000000000suck-4.3.4/suck.c000066400000000000000000002116271333033562000136070ustar00rootroot00000000000000#include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef HAVE_DIRENT_H # include #else # ifdef HAVE_SYS_NDIR_H # include # endif # ifdef HAVE_SYS_DIR_H # include # endif #endif #ifdef HAVE_LIMITS_H #include #endif #include #include #include #include #include #include #include #ifdef DMALLOC #include #endif #include "suck_config.h" #include "both.h" #include "suck.h" #include "suckutils.h" #include "dedupe.h" #include "phrases.h" #include "killfile.h" #include "timer.h" #include "active.h" #include "batch.h" #include "xover.h" #include "db.h" #ifdef MYSIGNAL #include #endif #ifdef CHECK_HISTORY #include "chkhistory.h" #endif /* function prototypes */ int get_articles(PMaster); int get_one_article(PMaster, int, long); int do_supplemental(PMaster); int restart_yn(PMaster); int scan_args(PMaster, int, char *[]); void do_cleanup(PMaster); int do_authenticate(PMaster); void load_phrases(PMaster); void free_phrases(void); int parse_args(PMaster, int, char *[]); int get_group_number(PMaster, char *); int do_supplemental(PMaster); int do_sup_bynr(PMaster, char *); int do_nodownload(PMaster); #ifdef MYSIGNAL RETSIGTYPE sighandler(int); void pause_signal(int, PMaster); enum {PAUSE_SETUP, PAUSE_DO}; /*------------------------------------------*/ int GotSignal = FALSE; /* the only static variable allowed, it's */ /* the only graceful way to handle a signal */ /*------------------------------------------*/ #endif /* set up for phrases */ char **both_phrases=default_both_phrases; char **suck_phrases=default_suck_phrases; char **timer_phrases=default_timer_phrases; char **chkh_phrases=default_chkh_phrases; char **dedupe_phrases=default_dedupe_phrases; char **killf_reasons=default_killf_reasons; char **killf_phrases=default_killf_phrases; char **killp_phrases=default_killp_phrases; char **sucku_phrases=default_sucku_phrases; char **active_phrases=default_active_phrases; char **batch_phrases=default_batch_phrases; char **xover_phrases=default_xover_phrases; char **xover_reasons=default_xover_reasons; enum { STATUS_STDOUT, STATUS_STDERR }; enum { RESTART_YES, RESTART_NO, RESTART_ERROR }; enum { ARG_NO_MATCH, ARG_ALWAYS_BATCH, ARG_BATCH_INN, ARG_BATCH_RNEWS, ARG_BATCH_LMOVE, \ ARG_BATCH_INNFEED, ARG_BATCH_POST, ARG_CLEANUP, ARG_DIR_TEMP, ARG_DIR_DATA, ARG_DIR_MSGS, \ ARG_DEF_ERRLOG, ARG_HOST, ARG_NO_POSTFIX, ARG_LANGUAGE, ARG_MULTIFILE, ARG_POSTFIX, \ ARG_QUIET, ARG_RNEWSSIZE, ARG_DEF_STATLOG, ARG_WAIT_SIG, ARG_ACTIVE, ARG_RECONNECT, \ ARG_DEBUG, ARG_ERRLOG, ARG_HISTORY, ARG_KILLFILE, ARG_KLOG_NONE, ARG_KLOG_SHORT, \ ARG_KLOG_LONG, ARG_MODEREADER, ARG_PORTNR, ARG_PASSWD, ARG_RESCAN, ARG_STATLOG, \ ARG_USERID, ARG_VERSION, ARG_WAIT, ARG_LOCALHOST, ARG_TIMEOUT, ARG_NRMODE, ARG_AUTOAUTH, \ ARG_NODEDUPE, ARG_NO_CHK_MSGID, ARG_READACTIVE, ARG_PREBATCH, ARG_SKIP_ON_RESTART, \ ARG_KLOG_NAME, ARG_USEGUI, ARG_XOVER, ARG_CONN_DEDUPE, ARG_POST_FILTER, ARG_CONN_ACTIVE, \ ARG_HIST_FILE, ARG_HEADER_ONLY, ARG_ACTIVE_LASTREAD, ARG_USEXOVER, ARG_RESETCOUNTER, \ ARG_LOW_READ, ARG_SHOW_GROUP, ARG_USE_SSL, ARG_LOCAL_SSL, ARG_BATCH_POST_NR, \ ARG_PASSWD_ENV, }; typedef struct Arglist{ const char *sarg; const char *larg; int nr_params; int flag; int errmsg; /* this is an index into suck_phrases */ } Args, *Pargs; const Args arglist[] = { {"a", "always_batch", 0, ARG_ALWAYS_BATCH, -1}, {"bi", "batch_inn", 1, ARG_BATCH_INN, 40}, {"br", "batch_rnews", 1, ARG_BATCH_RNEWS, 40}, {"bl", "batch_lmove", 1, ARG_BATCH_LMOVE, 40}, {"bf", "batch_innfeed",1, ARG_BATCH_INNFEED, 40}, {"bp", "batch_post", 0, ARG_BATCH_POST, -1}, {"bP", "batch_post_nr", 1, ARG_BATCH_POST_NR, 72}, {"c", "cleanup", 0, ARG_CLEANUP, -1}, {"dt", "dir_temp", 1, ARG_DIR_TEMP, 37}, {"dd", "dir_data", 1, ARG_DIR_DATA, 37}, {"dm", "dir_msgs", 1, ARG_DIR_MSGS, 37}, {"e", "def_error_log", 0, ARG_DEF_ERRLOG, -1}, {"f", "reconnect_dedupe", 0, ARG_CONN_DEDUPE, -1}, {"g", "header_only", 0, ARG_HEADER_ONLY, -1}, {"h", "host", 1, ARG_HOST, 51}, {"hl", "localhost", 1, ARG_LOCALHOST, 50}, {"i", "default_activeread", 1, ARG_ACTIVE_LASTREAD, 65}, {"k", "kill_no_postfix", 0, ARG_NO_POSTFIX, -1}, {"l", "language_file", 1, ARG_LANGUAGE, 47}, {"lr", "low_read", 0, ARG_LOW_READ, -1}, {"m", "multifile", 0, ARG_MULTIFILE, -1}, {"n", "number_mode", 0, ARG_NRMODE, -1}, {"p", "postfix", 1, ARG_POSTFIX, 36}, {"q", "quiet", 0, ARG_QUIET, -1}, {"r", "rnews_size", 1, ARG_RNEWSSIZE, 35}, {"rc", "reset_counter", 0, ARG_RESETCOUNTER, -1}, {"s", "def_status_log", 0, ARG_DEF_STATLOG, -1}, {"sg", "show_group", 0, ARG_SHOW_GROUP, -1}, #ifdef HAVE_LIBSSL {"ssl","use_ssl", 0, ARG_USE_SSL, -1}, #endif {"u", "auto_authorization", 0, ARG_AUTOAUTH, -1}, {"w", "wait_signal", 2, ARG_WAIT_SIG, 46}, {"x", "no_chk_msgid", 0, ARG_NO_CHK_MSGID, -1}, {"y", "post_filter", 1, ARG_POST_FILTER, 62}, {"z", "no_dedupe", 0, ARG_NODEDUPE, -1}, {"A", "active", 0, ARG_ACTIVE, -1}, {"AL", "read_active", 1, ARG_READACTIVE, 56}, {"B", "pre-batch", 0, ARG_PREBATCH, -1}, {"C", "reconnect", 1, ARG_RECONNECT, 49}, {"D", "debug", 0, ARG_DEBUG, -1}, {"E", "error_log", 1, ARG_ERRLOG, 41}, {"F", "reconnect_active", 0, ARG_CONN_ACTIVE, -1}, {"G", "use_gui", 0, ARG_USEGUI, -1}, {"H", "no_history", 0, ARG_HISTORY, -1}, {"HF", "history_file", 1, ARG_HIST_FILE, 64}, {"K", "killfile", 0, ARG_KILLFILE, -1}, {"L", "kill_log_none", 0, ARG_KLOG_NONE, -1}, {"LF", "kill_log_name", 1, ARG_KLOG_NAME, 61}, {"LS", "kill_log_short", 0, ARG_KLOG_SHORT, -1}, {"LL", "kill_log_long", 0, ARG_KLOG_LONG, -1}, {"M", "mode_reader", 0, ARG_MODEREADER, -1}, {"N", "portnr", 1, ARG_PORTNR, 45}, {"O", "skip_on_restart", 0, ARG_SKIP_ON_RESTART, -1}, {"P", "password", 1, ARG_PASSWD, 44}, {"Q", "password_env", 0, ARG_PASSWD_ENV, -1}, {"R", "no_rescan", 0, ARG_RESCAN, -1}, {"S", "status_log", 1, ARG_STATLOG, 42}, #ifdef HAVE_LIBSSL {"SSL" "local_use_ssl", 0, ARG_LOCAL_SSL, -1}, #endif #ifdef TIMEOUT {"T", "timeout", 1, ARG_TIMEOUT, 52}, #endif {"U", "userid", 1, ARG_USERID, 43}, {"V", "version", 0, ARG_VERSION, -1}, {"W", "wait", 2, ARG_WAIT, 46}, {"X", "no_xover", 0, ARG_XOVER, -1}, {"Z", "use_xover", 0, ARG_USEXOVER, -1}, }; #define MAX_ARG_PARAMS 2 /* max nr of params with any arg */ #define NR_ARGS (sizeof(arglist)/sizeof(arglist[0])) /*------------------------------------------------------------------*/ int main(int argc, char *argv[]) { struct stat sbuf; Master master; PList temp; PGroups ptemp; POverview pov; char *inbuf, **args, **fargs = NULL; int nr, resp, loop, fargc, retval = RETVAL_OK; #ifdef LOCKFILE const char *lockfile = NULL; #endif #ifdef MYSIGNAL #ifdef HAVE_SIGACTION struct sigaction sigs; #endif #endif /* initialize master structure */ master.head = master.curr = NULL; master.nritems = master.nrgot = 0; master.MultiFile = FALSE; master.msgs = stdout; master.sockfd = -1; master.status_file = FALSE; /* are status messages going to a file */ master.status_file_name = NULL; master.do_killfile = TRUE; master.do_chkhistory = TRUE; master.do_modereader = FALSE; master.always_batch = FALSE; master.rnews_size = 0L; master.batch = BATCH_FALSE; master.batchfile = NULL; master.cleanup = FALSE; master.portnr = DEFAULT_NNRP_PORT; master.host = getenv("NNTPSERVER"); /* the default */ master.pause_time = -1; master.pause_nrmsgs = -1; master.sig_pause_time = -1; master.sig_pause_nrmsgs = -1; master.killfile_log = KILL_LOG_LONG; /* do we log killed messages */ master.phrases = NULL; master.errlog = NULL; master.debug = FALSE; master.rescan = TRUE; /* do we do rescan on a restart */ master.quiet = FALSE; /* do we display BPS and msg count */ master.killp = NULL; /* pointer to killfile structure */ master.kill_ignore_postfix = FALSE; master.reconnect_nr = 0; /* how many x msgs do we disconnect and reconnect 0 = never */ master.innfeed = NULL; master.do_active = FALSE; /* do we scan the local active list */ master.localhost = NULL; master.groups = NULL; master.nrmode = FALSE; /* use nrs or msgids to get article */ master.auto_auth = FALSE; /* do we do auto authorization */ master.passwd = NULL; master.userid = NULL; master.no_dedupe = FALSE; master.chk_msgid = TRUE; /* do we check MsgID for trailing > */ master.activefile = NULL; master.prebatch = FALSE; /* do we try to batch any left over articles b4 we start? */ master.grpnr = -1; /* what group number are we currently on */ master.skip_on_restart = FALSE; master.kill_log_name = N_KILLLOG; master.use_gui = FALSE; master.do_xover = TRUE; master.conn_dedupe = FALSE; master.post_filter= NULL; master.conn_active = FALSE; master.history_file = HISTORY_FILE; master.header_only = FALSE; master.db = -1; master.active_lastread = ACTIVE_DEFAULT_LASTREAD; master.use_xover = FALSE; master.xoverview = NULL; master.resetcounter = FALSE; master.low_read = FALSE; master.show_group = FALSE; master.do_ssl = FALSE; master.ssl_struct = NULL; master.local_ssl = FALSE; master.local_ssl_struct = NULL; master.batch_post_nr = 0; master.passwd_env = FALSE; /* have to do this next so if set on cmd line, overrides this */ #ifdef N_PHRASES /* in case someone nukes def */ if(stat(N_PHRASES, &sbuf) == 0 && S_ISREG(sbuf.st_mode)) { /* we have a regular phrases file make it the default */ master.phrases = N_PHRASES; } #endif /* allow no args, only the hostname, or hostname as first arg */ /* also have to do the file argument checking */ switch(argc) { case 1: break; case 2: /* the fargs == NULL test so only process first file name */ if(argv[1][0] == FILE_CHAR) { if((fargs = build_args(&argv[1][1], &fargc)) != NULL) { retval = scan_args(&master, fargc, fargs); } } else if(argv[1][0] == '-') { /* in case of suck -V */ retval = scan_args(&master, 1, &(argv[1])); } else{ master.host = argv[1]; } break; default: for(loop=1;loop 0) { /* if we don't have any messages, we don't need to do this */ if(master.no_dedupe == FALSE) { dedupe_list(&master); } #ifdef CHECK_HISTORY if(master.do_chkhistory == TRUE) { chkhistory(&master); } #endif print_phrases(master.msgs,suck_phrases[20], str_int(master.nritems), NULL); } } if(retval == RETVAL_OK) { if(master.nritems == 0) { print_phrases(master.msgs,suck_phrases[3], NULL); retval = RETVAL_NOARTICLES; } else { /* write out restart db */ retval = db_write(&master); /* reconnect after dedupe, in case of time out due to long dedupe time*/ if(retval == RETVAL_OK && master.conn_dedupe == TRUE) { retval = do_connect(&master, CONNECT_AGAIN); } if(retval == RETVAL_OK) { retval = get_articles(&master); } } } if(retval == RETVAL_OK) { /* if we got disconnected above, don't do this */ /* send quit, and get reply */ sputline(master.sockfd,"quit\r\n", master.do_ssl, master.ssl_struct); if(master.debug == TRUE) { do_debug("Sending command: quit\n"); } do { resp = sgetline(master.sockfd, &inbuf, master.do_ssl, master.ssl_struct); if(resp>0) { if(master.debug == TRUE) { do_debug("Quitting GOT: %s", inbuf); } number(inbuf, &nr); } }while(nr != 205 && resp > 0); } } if(master.sockfd >= 0) { disconnect_from_nntphost(master.sockfd, master.do_ssl, &master.ssl_struct); print_phrases(master.msgs,suck_phrases[4], master.host, NULL); } if(master.debug == TRUE) { do_debug("retval=%d (RETVAL_OK=%d), m.nrgot=%d, m.batch=%d\n", retval, RETVAL_OK, master.nrgot, master.batch); } if((retval == RETVAL_OK || master.always_batch == TRUE) && master.nrgot > 0 && master.header_only == FALSE) { switch(master.batch) { case BATCH_INNXMIT: do_post_filter(&master); do_innbatch(&master); break; case BATCH_RNEWS: do_post_filter(&master); do_rnewsbatch(&master); break; case BATCH_LMOVE: do_post_filter(&master); do_lmovebatch(&master); break; case BATCH_LIHAVE: do_post_filter(&master); do_localpost(&master); break; default: break; } } if((retval == RETVAL_NOARTICLES || retval == RETVAL_OK) && master.cleanup == TRUE) { print_phrases(master.msgs, suck_phrases[7], NULL); do_cleanup(&master); } /* close out status log */ if(master.msgs != NULL && master.msgs != stdout && master.msgs != stderr) { fclose(master.msgs); } if(master.head != NULL) { /* clean up memory */ master.curr = master.head; while(master.curr != NULL) { temp = (master.curr)->next; free_one_node(master.curr); master.curr = temp; } } /* free up group list */ while (master.groups != NULL) { ptemp=(master.groups)->next; free(master.groups); master.groups = ptemp; } /* free up overview.fmt list */ while (master.xoverview != NULL) { pov = (master.xoverview)->next; if((master.xoverview)->header != NULL) { free((master.xoverview)->header); } free(master.xoverview); master.xoverview = pov; } #ifdef KILLFILE free_killfile(master.killp); #endif free_killfile(master.xoverp); if(fargs != NULL) { free_args(fargc, fargs); } #ifdef LOCKFILE lockfile = full_path(FP_GET, FP_TMPDIR, N_LOCKFILE); if(lockfile != NULL) { unlink(lockfile); } #endif } free_phrases(); /* do this last so everything is displayed correctly */ exit(retval); } /*------------------------------------------------------------*/ int do_connect(PMaster master, int which_time) { char *inbuf; int nr, resp, retval = RETVAL_OK; FILE *fp; if(which_time != CONNECT_FIRST) { /* close down previous connection */ sputline(master->sockfd, "quit\r\n", master->do_ssl, master->ssl_struct); do { resp = sgetline(master->sockfd, &inbuf, master->do_ssl, master->ssl_struct); if(resp>0) { if(master->debug == TRUE) { do_debug("Reconnect GOT: %s", inbuf); } number(inbuf, &nr); } }while(nr != 205 && resp > 0); disconnect_from_nntphost(master->sockfd, master->do_ssl, &master->ssl_struct); /* now reset everything */ if(master->curr != NULL) { (master->curr)->sentcmd = FALSE; } master->grpnr = -1; } if(master->debug == TRUE) { do_debug("Connecting to %s on port %d\n", master->host, master->portnr); } fp = (which_time == CONNECT_FIRST) ? master->msgs : NULL; master->sockfd = connect_to_nntphost( master->host, NULL, 0, fp, master->portnr, master->do_ssl, &master->ssl_struct); if(master->sockfd < 0 ) { retval = RETVAL_ERROR; } else if(sgetline(master->sockfd, &inbuf, master->do_ssl, master->ssl_struct) < 0) { /* Get the announcement line */ retval = RETVAL_ERROR; } else { if(master->debug == TRUE) { do_debug("Got: %s", inbuf); } if(which_time == CONNECT_FIRST) { fprintf(master->msgs ,"%s", inbuf ); } /* check to see if we have to do authorization now */ number(inbuf, &resp); if(resp == 480 ) { retval = do_authenticate(master); } if(retval == RETVAL_OK && master->do_modereader == TRUE) { retval = send_command(master, "mode reader\r\n", &inbuf, 0); if(retval == RETVAL_OK) { /* Again the announcement */ if(which_time == CONNECT_FIRST) { fprintf(master->msgs ,"%s",inbuf); } } } if(master->auto_auth == TRUE) { if(master->passwd == NULL || master->userid == NULL) { error_log(ERRLOG_REPORT, suck_phrases[55], NULL); retval = RETVAL_ERROR; } else { /* auto authorize */ retval = do_authenticate(master); } } } return retval; } /*--------------------------------------------------------------------*/ int get_message_index(PMaster master) { long lastread; int nrread, retval, maxread; char buf[MAXLINLEN], group[512]; FILE *ifp,*tmpfp; retval = RETVAL_OK; ifp = tmpfp = NULL; TimerFunc(TIMER_START, 0, NULL); if((ifp = fopen(full_path(FP_GET, FP_DATADIR, N_OLDRC), "r" )) == NULL) { MyPerror(full_path(FP_GET, FP_DATADIR, N_OLDRC)); retval = RETVAL_ERROR; } else if((tmpfp = fopen(full_path(FP_GET, FP_TMPDIR, N_NEWRC), "w" )) == NULL) { MyPerror(full_path(FP_GET, FP_TMPDIR, N_NEWRC)); retval = RETVAL_ERROR; } while(retval == RETVAL_OK && fgets(buf, MAXLINLEN-1, ifp) != NULL) { if(buf[0] == SUCKNEWSRC_COMMENT_CHAR) { /* skip this group */ fputs(buf, tmpfp); print_phrases(master->msgs, suck_phrases[8], buf, NULL); } else { maxread = -1; /* just in case */ nrread = sscanf(buf, "%s %ld %d\n", group, &lastread, &maxread); if ( nrread < 2 || nrread > 3) { error_log(ERRLOG_REPORT, suck_phrases[9], buf, NULL); fputs(buf, tmpfp); /* rewrite the line */ } else if(maxread == 0) { /* just rewrite the line */ fputs(buf, tmpfp); } else { retval = do_one_group(master, buf, group, tmpfp, lastread, maxread); } } } TimerFunc(TIMER_TIMEONLY, 0,master->msgs); if(retval == RETVAL_OK) { print_phrases(master->msgs, suck_phrases[16], str_int(master->nritems), NULL); } else if(ifp != NULL) { /* this is in case we had to abort the above while loop (due to loss of pipe to server) and */ /* we hadn't finished writing out the suck.newrc, this finishes it up. */ do { fputs(buf, tmpfp); } while(fgets(buf, MAXLINLEN-1, ifp) != NULL); } if(tmpfp != NULL) { fclose(tmpfp); } if(ifp != NULL) { fclose(ifp); } return retval; } /*-----------------------------------------------------------------------------------------------------*/ int do_one_group(PMaster master, char *buf, char *group, FILE *newrc, long lastread, int maxread) { char *sp, *inbuf, cmd[MAXLINLEN]; long count,low,high; int response,retval,i; retval = RETVAL_OK; sprintf(cmd,"group %s\r\n",group); if(send_command(master,cmd,&inbuf,0) != RETVAL_OK) { retval = RETVAL_ERROR; } else { sp = number(inbuf, &response); if(response != 211) { fputs(buf, newrc); /* rewrite line AS IS in newrc */ /* handle group not found */ switch(response) { case 411: error_log(ERRLOG_REPORT, suck_phrases[11], group, NULL); break; case 500: error_log(ERRLOG_REPORT, suck_phrases[48], NULL); break; default: error_log(ERRLOG_REPORT, suck_phrases[12],group,str_int(response),NULL); retval = RETVAL_ERROR; /* bomb out on wacko errors */ break; } } else { sp = get_long(sp, &count); sp = get_long(sp, &low); sp = get_long(sp, &high); fprintf(newrc, "%s %ld", group, high); if(maxread > 0) { fprintf(newrc, " %d", maxread); } fputs("\n", newrc); /* add a sanity check in case remote host changes its numbering scheme */ /* the > 0 is needed, since if a nnrp site has no article it will reset */ /* the count to 0. So not an error */ if(lastread > high && high > 0) { if(master->resetcounter == TRUE) { /* reset lastread to low, so we pick up all articles in the group */ lastread = low; print_phrases(master->msgs,suck_phrases[71],group,str_int(high),str_int(low),NULL); } else { print_phrases(master->msgs,suck_phrases[13],group,str_int(high),NULL); } } if(lastread < 0 ) { /* if a neg number, get the last X nr of messages, handy for starting */ /* a new group off ,say -100 will get the last 100 messages */ lastread += high; /* this works since lastread is neg */ if(lastread < 0) { lastread = 0; /* just to be on the safeside */ } } /* this has to be >= 0 since if there are no article on server high = 0 */ /* so if we write out 0, we must be able to handle zero as valid lastread */ /* the count > 0 so if no messages available we don't even try */ /* or if low > high no messages either */ if (low <= high && count > 0 && lastread < high && lastread >= 0) { /* add group name to list of groups */ if(lastread < low) { lastread = low - 1; } /* now set the max nr of messages to read */ if(maxread > 0 && high-maxread > lastread) { if(master->low_read == TRUE) { /* instead of limiting from the high-water mark (the latest articles) */ /* limit from the low-water mark (the oldest articles) */ high = (lastread+1) + maxread; } else { lastread = high-maxread; } print_phrases(master->msgs, suck_phrases[14], group, str_int(maxread), NULL); } print_phrases(master->msgs, suck_phrases[15], group, str_long(high-lastread), str_long(lastread+1), str_long(high), NULL); if(master->xoverp != NULL) { /* do this via the xover killfile command */ retval = do_group_xover(master, group, lastread+1, high); } /* do we use xover or xhdr to get our article list */ if(master->use_xover == TRUE) { retval = get_xover(master, group, lastread+1, high); } else if((master->xoverp == NULL) || (retval == RETVAL_NOXOVER)) { /* do this the normal way */ sprintf(cmd, "xhdr Message-ID %ld-%ld\r\n", lastread+1,high); i = send_command(master,cmd,&inbuf,221); if(i != RETVAL_OK) { retval = RETVAL_ERROR; } else { do { if(sgetline(master->sockfd, &inbuf, master->do_ssl, master->ssl_struct) < 0) { retval = RETVAL_ERROR; } else if (*inbuf != '.' ) { retval = allocnode(master, inbuf, MANDATORY_OPTIONAL, group, 0L); } } while (retval == RETVAL_OK && *inbuf != '.' && *(inbuf+1) != '\n'); } /* end if response */ } /* end if xover */ } /* end if lastread */ } /* end response */ } /* end else */ if(retval == RETVAL_ERROR) { error_log(ERRLOG_REPORT, suck_phrases[59], NULL); } return retval; } /*-----------------------------------------------------------*/ int get_articles(PMaster master) { int retval, logcount, i, grpnr, grplen; long loop, downloaded; PGroups grps; const char *grpname; const char *empty = ""; #ifdef HAVE_GETTIMEOFDAY double bps; #endif #ifdef KILLFILE int ( *get_message)(PMaster, int, long); /* which function will we use get_one_article or get_one_article_kill) */ grpnr = -1; grps = NULL; grpname = empty; grplen = 0; /* do we use killfiles? */ get_message = (master->killp == NULL) ? get_one_article : get_one_article_kill; #endif retval = RETVAL_OK; downloaded = loop = 0; /* used to track how many downloaded, for reconnect option */ /* figure out how many digits wide the articleCount is for display purposes */ /* this used to be log10()+1, but that meant I needed the math library */ for(logcount=1, i=master->nritems; i > 9 ; logcount++) { i /= 10; } if(master->MultiFile == TRUE && checkdir(full_path(FP_GET, FP_MSGDIR, NULL)) == FALSE) { retval = RETVAL_ERROR; } else { retval = db_open(master); if(retval == RETVAL_OK) { master->curr = master->head; if(master->batch == BATCH_INNFEED) { /* open up batch file for appending to */ if((master->innfeed = fopen(master->batchfile, "a")) == NULL) { MyPerror(master->batchfile); } } else if(master->batch == BATCH_LIHAVE) { if((master->innfeed = fopen(full_path(FP_GET, FP_TMPDIR, master->batchfile), "a")) == NULL) { MyPerror(full_path(FP_GET, FP_TMPDIR, master->batchfile)); retval = RETVAL_ERROR; } } TimerFunc(TIMER_START, 0, NULL); #ifdef MYSIGNAL while(retval == RETVAL_OK && master->curr != NULL && GotSignal == FALSE) { #else while(retval == RETVAL_OK && master->curr != NULL) { #endif if(master->debug == TRUE) { do_debug("Article nr = %s mandatory = %c\n", (master->curr)->msgnr, (master->curr)->mandatory); } loop++; if((master->curr)->downloaded == FALSE) { /* we haven't yet downloaded this article */ downloaded++; /* to be polite to the server, lets allow for a pause every once in a while */ if(master->pause_time > 0 && master->pause_nrmsgs > 0) { if((downloaded > 0) && (downloaded % master->pause_nrmsgs == 0)) { sleep(master->pause_time); } } if(master->status_file == FALSE && master->quiet == FALSE) { /* if we are going to a file, we don't want all of these articles printed */ /* or if quiet flag is set */ /* this stuff doesn't go thru print_phrases so I can keep spacing right */ /* and I only print numbers and the BPS */ #ifdef HAVE_GETTIMEOFDAY bps = TimerFunc(TIMER_GET_BPS, 0, master->msgs); #endif if(master->use_gui == TRUE) { /* this stuff is formatted by the GUI so we put it out in a format it likes */ #ifndef HAVE_GETTIMEOFDAY fprintf(master->msgs, "---%ld+++\n",master->nritems - loop); #else fprintf(master->msgs, "---%ld+++%f\n", master->nritems - loop, bps); #endif /* HAVE_GETTIMEOFDAY */ } else { if(master->show_group == TRUE) { /* add the group name (if available) to the line printed */ if((master->curr)->groupnr >= 0) { if((master->curr)->groupnr != grpnr) { grpname = empty; grpnr = (master->curr)->groupnr; /* new group name find it */ grps = master->groups; while(grps != NULL && grps->nr != grpnr) { grps = grps->next; } if(grps != NULL) { grpname = grps->group; /* calculate max len for format of printf*/ /* which erases the previous group name */ if(strlen(grpname) > grplen) { grplen = strlen(grpname); } } } } else { grpnr = -1; grpname = empty; } } #ifndef HAVE_GETTIMEOFDAY fprintf(master->msgs, "%5ld %-*s\r",master->nritems - loop, grplen, grpname); #else fprintf(master->msgs, "%5ld %9.1f %s %-*s\r",master->nritems-loop,bps,suck_phrases[5], grplen, grpname); #endif /* HAVE_GETTIMEOFDAY */ } fflush(master->msgs); /* so message gets printed now */ } #ifdef KILLFILE /* get one article */ retval = (*get_message)(master, logcount, loop); #else retval = get_one_article(master, logcount, loop); #endif if(retval == RETVAL_OK ) { retval = db_mark_dled(master, master->curr); } } master->curr = (master->curr)->next; /* get next article */ /* to be NOT polite to the server reconnect every X msgs to combat the INND */ /* LIKE_PULLERS=DONT. This is last in loop, so if problem can abort gracefully */ if((master->reconnect_nr > 0) && (downloaded > 0) && (downloaded % master->reconnect_nr == 0)) { retval = do_connect(master, CONNECT_AGAIN); if(master->curr != NULL) { (master->curr)->sentcmd = FALSE; /* if we sent command force resend */ } } // do a local post every x number of articles if(master->batch == BATCH_LIHAVE && master->batch_post_nr > 0 && master->nrgot >= master->batch_post_nr) { fclose(master->innfeed); master->innfeed = NULL; do_post_filter(master); retval = do_localpost(master); if(retval == RETVAL_OK) { master->nrgot = 0; // so if no more articles left we don't try to post in main routine if((master->innfeed = fopen(full_path(FP_GET, FP_TMPDIR, master->batchfile), "a")) == NULL) { MyPerror(full_path(FP_GET, FP_TMPDIR, master->batchfile)); retval = RETVAL_ERROR; } } } } /* end while */ db_close(master); if(retval == RETVAL_OK && master->nritems == loop) { db_delete(master); } if(retval == RETVAL_OK) { TimerFunc(TIMER_TOTALS, 0, master->msgs); } if(master->innfeed != NULL) { fclose(master->innfeed); } } } return retval; } /*-----------------------------------------------*/ /* add items from supplemental list to link list */ /* ----------------------------------------------*/ int do_supplemental(PMaster master) { int retval, oldkept; FILE *fp; char linein[MAXLINLEN+1]; retval = RETVAL_OK; oldkept = master->nritems; if((fp = fopen(full_path(FP_GET, FP_DATADIR, N_SUPPLEMENTAL), "r")) != NULL) { print_phrases(master->msgs, suck_phrases[17], NULL); while(retval == RETVAL_OK && fgets(linein, MAXLINLEN, fp) != NULL) { if(linein[0] == '!') { retval = do_sup_bynr(master, linein); } else if(linein[0] == '<') { retval = allocnode(master, linein, MANDATORY_YES, NULL, 0L); } else { error_log(ERRLOG_REPORT, suck_phrases[18], linein, NULL); } } print_phrases(master->msgs, suck_phrases[19], str_int(master->nritems-oldkept), \ str_int(master->nritems), NULL); fclose(fp); } return retval; } /*------------------------------------------------------------------------------------------*/ int do_sup_bynr(PMaster master, char *linein) { int retval = RETVAL_OK; /* this routine takes in a line formated !group_name article_nr */ /* gets it's Msg-ID (via XHDR), then adds it to our list of articles */ /* as a mandatory article to download */ char grpname[MAX_GRP_LEN], cmd[MAXLINLEN], *resp; int i, done; long nrlow, nrhigh; if(master->debug == TRUE) { do_debug("supplemental adding %s", linein); } i = sscanf(linein, "!%s %ld-%ld", grpname, &nrlow, &nrhigh); if(i < 2) { error_log(ERRLOG_REPORT, suck_phrases[18], linein, NULL); } else { sprintf(cmd,"group %s\r\n",grpname); if(send_command(master,cmd,NULL,211) == RETVAL_OK) { if(i == 2) { sprintf(cmd,"xhdr Message-ID %ld\r\n",nrlow); } else { sprintf(cmd,"xhdr Message-ID %ld-%ld\r\n",nrlow,nrhigh); } if(send_command(master,cmd,NULL,221) == RETVAL_OK) { /* now have to get message-id and the . */ done = FALSE; while( done == FALSE) { if(sgetline(master->sockfd, &resp, master->do_ssl, master->ssl_struct) < 0) { retval = RETVAL_ERROR; done = TRUE; } else { if(master->debug == TRUE) { do_debug("Got %s", resp); } if(*resp == '.') { done = TRUE; } else { retval = allocnode(master, resp, MANDATORY_YES, grpname, 0L); } } } } } } return retval; } /*---------------------------------------------------------------------*/ int do_nodownload(PMaster master) { int i, retval = RETVAL_OK; FILE *fp; PList item, prev; char linein[MAXLINLEN+1]; long nrlines = 0, nrnuked = 0; if((fp = fopen(full_path(FP_GET, FP_DATADIR, N_NODOWNLOAD), "r")) != NULL) { print_phrases(master->msgs, suck_phrases[68], NULL); while(retval == RETVAL_OK && fgets(linein, MAXLINLEN, fp) != NULL) { nrlines++; i = strlen(linein) - 1; /* so it points at last char */ /* strip off nl */ if(linein[i] == '\n') { linein[i] = '\0'; i--; } /* strip off extra spaces on the end */ while(isspace(linein[i])){ linein[i] = '\0'; i--; } item = master->head; prev = NULL; /* find start of msgid */ if(linein[0] != '<') { error_log(ERRLOG_REPORT, suck_phrases[69], linein, NULL); } else { if(master->debug == TRUE) { do_debug("Checking Nodownload - %s\n", linein); } while((item != NULL) && (cmp_msgid(linein, item->msgnr) == FALSE)) { prev = item; item = item->next; } if(item != NULL) { /* found a match, remove from the list */ if(master->debug == TRUE) { do_debug("Matched, nuking from list\n"); } nrnuked++; master->nritems--; if(item == master->head) { /* change top of list */ master->head = (master->head)->next; } else { /* its not the end of the list */ prev->next = item->next; free_one_node(item); } } } } fclose(fp); print_phrases(master->msgs, suck_phrases[70], str_long(nrlines), str_long(nrnuked), str_long(master->nritems), NULL); } return retval; } /*----------------------------------------------------------------------*/ int get_group_number(PMaster master, char *grpname) { PGroups grps, gptr; int groupnr = 0; /* first, find out if it doesn't exist already */ gptr = master->groups; while(gptr != NULL && groupnr == 0) { if(strcmp(gptr->group, grpname) == 0) { /* bingo */ groupnr = gptr->nr; } else { gptr = gptr->next; } } if(groupnr == 0) { /* add group to group list and get group nr */ if((grps = malloc(sizeof(Groups))) == NULL) { /* out of memory */ error_log(ERRLOG_REPORT, suck_phrases[22], NULL); } else { grps->next = NULL; strcpy(grps->group, grpname); /* now add to list and count groupnr */ if(master->groups == NULL) { grps->nr = 1; master->groups = grps; } else { gptr = master->groups; while(gptr->next != NULL) { gptr = gptr->next; } gptr->next = grps; grps->nr = gptr->nr + 1; } groupnr = grps->nr; if(master->debug == TRUE) { do_debug("Adding to group list: %d %s\n", grps->nr, grps->group); } } } return groupnr; } /*-----------------------------------------------------------------------*/ int allocnode(PMaster master, char *linein, int mandatory, char *group, long msgnr_in) { /* if msgnr_in is not filled in (0), then parse the msgnr off the linein */ /* if allocate memory here, must free in free_one_node */ PList ptr = NULL; char *end_ptr, *st_ptr; static PList curr = NULL; /* keep track of current end of list */ int groupnr = 0, retval = RETVAL_OK; long msgnr = 0; static int warned = FALSE; /* get the article nr */ if(group == NULL) { /* we're called by do_supplemental */ st_ptr = linein; } else { if (msgnr_in > 0) { /* we're prob called by xover code */ st_ptr = linein; msgnr = msgnr_in; } else { st_ptr = get_long(linein,&msgnr); if(msgnr == 0 && warned == FALSE) { warned = TRUE; error_log(ERRLOG_REPORT, suck_phrases[53], NULL); } } if(msgnr > 0 && group != NULL) { groupnr = get_group_number(master, group); } } if(retval == RETVAL_OK) { /* find the message id */ while(*st_ptr != '<' && *st_ptr != '\0') { st_ptr++; } end_ptr = st_ptr; while(*end_ptr != '>' && *end_ptr != '\0') { end_ptr++; } if((*st_ptr != '<') || ( master->chk_msgid == TRUE && *end_ptr != '>')) { error_log(ERRLOG_REPORT, suck_phrases[21], linein, NULL); } else { *(end_ptr+1) = '\0'; /* ensure null termination */ if((ptr = malloc(sizeof(List))) == NULL) { error_log(ERRLOG_REPORT, suck_phrases[22], NULL); retval = RETVAL_ERROR; } else if(strlen(st_ptr) >= MAX_MSGID_LEN) { error_log(ERRLOG_REPORT, suck_phrases[63], group, msgnr, NULL); free(ptr); } else { strcpy(ptr->msgnr, st_ptr); ptr->next = NULL; ptr->mandatory = (char) mandatory; ptr->sentcmd = FALSE; /* have we sent command to remote */ ptr->nr = msgnr; ptr->groupnr = groupnr; ptr->downloaded = FALSE; ptr->dbnr = 0L; ptr->delete = FALSE; if(master->debug == TRUE) { do_debug("MSGID %s NR %d GRP %d MANDATORY %c added\n",ptr->msgnr, ptr->nr, ptr->groupnr, ptr->mandatory); } /* now put on list */ if( curr == NULL) { if(master->head == NULL) { /* first node */ master->head = curr = ptr; } else { /* came in with a restart, find end of list */ curr = master->head; while(curr->next != NULL) { curr = curr->next; } /* now add node on */ curr->next = ptr; curr = ptr; } } else { curr->next = ptr; curr = ptr; } master->nritems++; } } } return retval; } /*------------------------------------------------------------------------*/ void free_one_node(PList node) { free(node); } /*----------------------------------------------------------------------------------*/ const char *build_command(PMaster master, const char *cmdstart, PList article) { static char cmd[MAXLINLEN+1]; static int warned = FALSE; char *resp; char grpcmd[MAXLINLEN+1]; PGroups grps; /* build command to get article/head/body */ /* if nrmode is on send group cmd if needed */ if(master->nrmode == TRUE) { if(article->nr == 0) { master->nrmode = FALSE; } else if(master->grpnr != article->groupnr) { grps = master->groups; while(grps != NULL && grps->nr != article->groupnr) { grps = grps->next; } if(grps != NULL) { /* okay send group command */ snprintf(grpcmd, MAXLINLEN, "GROUP %s\r\n", grps->group); if(send_command(master, grpcmd, &resp, 211) != RETVAL_OK) { master->nrmode = FALSE; /* can't chg groups turn it off */ } else { /* so don't redo group command on next one */ master->grpnr = grps->nr; } } } if(master->nrmode == TRUE) { /* everything hunky dory, we've switched groups, and got a good nr */ snprintf(cmd, MAXLINLEN, "%s %ld\r\n", cmdstart, article->nr); } else if(warned == FALSE) { /* tell of the switch to nonnr mode */ warned = TRUE; error_log(ERRLOG_REPORT, suck_phrases[54], NULL); } } /* the if is in case we changed it above */ if(master->nrmode == FALSE) { snprintf(cmd, MAXLINLEN, "%s %s\r\n", cmdstart, article->msgnr); } return cmd; } /*----------------------------------------------------------------------------------*/ int get_one_article(PMaster master, int logcount, long itemon) { int nr, len, retval = RETVAL_OK; char buf[MAXLINLEN+1]; const char *cmd = "", *tname = NULL; char fname[PATH_MAX+1]; char *resp; PList plist; FILE *fptr = stdout; /* the default */ fname[0] = '\0'; /* just in case */ /* first send command to get article if not already sent */ if((master->curr)->sentcmd == FALSE) { cmd = (master->header_only == FALSE) ? build_command(master, "article", master->curr) : build_command(master, "head", master->curr); if(master->debug == TRUE) { do_debug("Sending command: \"%s\"",cmd); } sputline(master->sockfd, cmd, master->do_ssl, master->ssl_struct); (master->curr)->sentcmd = TRUE; } plist = (master->curr)->next; if(plist != NULL && master->batch_post_nr == 0) { /* if batch_post_nr > 0 we're doing periodic batch posts */ /* which confuses the remote server so we can't use pipelining */ if(master->nrmode == FALSE || plist->groupnr == (master->curr)->groupnr) { /* can't use pipelining if nrmode on, cause of GROUP command I'll have to send*/ if(plist->sentcmd == FALSE) { /* send next command */ cmd = (master->header_only == FALSE) ? build_command(master, "article", plist) : build_command(master, "head", plist); if(master->debug == TRUE) { do_debug("Sending command: \"%s\"",cmd); } sputline(master->sockfd, cmd, master->do_ssl, master->ssl_struct); plist->sentcmd = TRUE; } } } /* now while the remote is finding the article lets set up */ if(master->MultiFile == TRUE) { /* open file */ /* file name will be ####-#### ex 001-166 (nron,total) */ sprintf(buf,"%0*ld-%d", logcount, itemon ,master->nritems); /* the strcpy to avoid wiping out fname in second call to full_path */ strcpy(fname, full_path(FP_GET, FP_MSGDIR, buf)); strcat(buf, N_TMP_EXTENSION); /* add tmp extension */ tname = full_path(FP_GET, FP_TMPDIR, buf); /* temp file name */ if(master->debug == TRUE) { do_debug("File name = \"%s\" temp = \"%s\"", fname, tname); } if((fptr = fopen(tname, "w")) == NULL) { MyPerror(tname); retval = RETVAL_ERROR; } } /* okay hopefully by this time the remote end is ready */ if((len = sgetline(master->sockfd, &resp, master->do_ssl, master->ssl_struct)) < 0) { retval = RETVAL_ERROR; } else { if(master->debug == TRUE) { do_debug("got answer: %s", resp); } TimerFunc(TIMER_ADDBYTES, len, NULL); number(resp, &nr); if(nr == 480) { /* got to authorize, negate send-ahead for next msg */ if(plist != NULL) { plist->sentcmd = FALSE; /* cause its gonna error out in do_auth() */ } if(do_authenticate(master) == RETVAL_OK) { /* resend command for current article */ cmd = build_command(master, "article", master->curr); if(master->debug == TRUE) { do_debug("Sending command: \"%s\"",cmd); } sputline(master->sockfd, cmd, master->do_ssl, master->ssl_struct); len = sgetline(master->sockfd, &resp, master->do_ssl, master->ssl_struct); if(master->debug == TRUE) { do_debug("got answer: %s", resp); } TimerFunc(TIMER_ADDBYTES, len, NULL); number(resp, &nr); } else { retval = RETVAL_ERROR; } } /* 221 = header 220 = article */ if(nr == 220 || nr == 221 ) { /* get the article */ if((retval = get_a_chunk(master, fptr)) == RETVAL_OK) { master->nrgot++; if (fptr == stdout) { /* gracefully end the message in stdout version */ fputs(".\n", stdout); } } } else { /* don't return RETVAL_ERROR so we can get next article */ error_log(ERRLOG_REPORT, suck_phrases[29], cmd ,resp, NULL); } } if(fptr != NULL && fptr != stdout) { fclose(fptr); } if(master->MultiFile == TRUE) { if(retval == RETVAL_ERROR || (nr != 221 && nr != 220)) { unlink(tname); } /* now rename it to the permanent file name */ else { move_file(tname, fname); if((master->batch == BATCH_INNFEED || master->batch == BATCH_LIHAVE) && master->innfeed != NULL) { /* write path name and msgid to file */ fprintf(master->innfeed, "%s %s\n", fname, (master->curr)->msgnr); fflush(master->innfeed); /* so it gets written sooner */ } } } return retval; } /*---------------------------------------------------------------------------*/ int get_a_chunk(PMaster master, FILE *fptr) { int done, partial, len, retval; char *inbuf; size_t nr; retval = RETVAL_OK; done = FALSE; partial = FALSE; len = 0; /* just to get rid of a compiler warning */ /* partial = was the previous line a complete line or not */ /* this is needed to avoid a scenario where the line is MAXLINLEN+1 */ /* long and the last character is a ., which would make us think */ /* that we are at the end of the article when we actually aren't */ while(done == FALSE && (len = sgetline(master->sockfd, &inbuf, master->do_ssl, master->ssl_struct)) >= 0) { TimerFunc(TIMER_ADDBYTES, len, NULL); if(inbuf[0] == '.' && partial == FALSE) { if(len == 2 && inbuf[1] == '\n') { done = TRUE; } else { /* handle double dots IAW RFC977 2.4.1*/ inbuf++; /* move past first dot */ len--; } } if(done == FALSE) { if(retval == RETVAL_OK) { nr = fwrite(inbuf, sizeof(inbuf[0]), len, fptr); fflush(fptr); if(nr != len) { retval = RETVAL_ERROR; error_log(ERRLOG_REPORT, suck_phrases[57], NULL); } } partial= (len==MAXLINLEN&&inbuf[len-1]!='\n') ? TRUE : FALSE; } } if(len < 0) { retval = RETVAL_ERROR; } return retval; } /*-----------------------------------------------------------------------------*/ int restart_yn(PMaster master) { int done, retval = RESTART_NO; PList itemon; long itemnr; const char *fname; struct stat buf; fname = full_path(FP_GET, FP_TMPDIR, N_DBFILE); if(stat(fname, &buf) == 0) { /* restart file exists */ retval = db_read(master); if(retval == RETVAL_OK) { /* find out where we stopped */ itemon = master->head; itemnr = 0L; done = FALSE; while(itemon != NULL && done == FALSE) { itemnr++; if(itemon->downloaded == FALSE) { /* so that we don't think we already sent command */ itemon->sentcmd = FALSE; if(master->skip_on_restart == TRUE) { /* mark one more */ print_phrases(master->msgs, suck_phrases[60], NULL); itemon->downloaded = TRUE; } else { itemnr--; /* don't count this one */ } done = TRUE; } itemon = itemon->next; } } } return retval; } /*-----------------------------------------------------------------*/ #ifdef MYSIGNAL RETSIGTYPE sighandler(int what) { switch(what) { case PAUSESIGNAL: pause_signal(PAUSE_DO, NULL); /* if we don't do this, the next time called, we'll abort */ /* signal(PAUSESIGNAL, sighandler); */ /* signal_block(MYSIGNAL_SETUP); */ /* just to be on the safe side */ break; case MYSIGNAL: case MYSIGNAL2: default: error_log(ERRLOG_REPORT, suck_phrases[24], NULL); GotSignal = TRUE; } } /*----------------------------------------------------------------*/ void pause_signal(int action, PMaster master) { int x, y; static PMaster psave = NULL; switch(action) { case PAUSE_SETUP: psave = master; break; case PAUSE_DO: if(psave == NULL) { error_log(ERRLOG_REPORT, suck_phrases[25], NULL); } else { /* swap pause_time and pause_nrmsgs with the sig versions */ x = psave->pause_time; y = psave->pause_nrmsgs; psave->pause_time = psave->sig_pause_time; psave->pause_nrmsgs = psave->sig_pause_nrmsgs; psave->sig_pause_time = x; psave->sig_pause_nrmsgs = y; print_phrases(psave->msgs, suck_phrases[26], NULL); } } } #endif /*----------------------------------------------------------------*/ int send_command(PMaster master, const char *cmd, char **ret_response, int good_response) { /* this is needed so can do user authorization */ int len, retval = RETVAL_OK, nr; char *resp; if(master->debug == TRUE) { do_debug("sending command: %s", cmd); } sputline(master->sockfd, cmd, master->do_ssl, master->ssl_struct); len = sgetline(master->sockfd, &resp, master->do_ssl, master->ssl_struct); if( len < 0) { retval = RETVAL_ERROR; } else { if(master->debug == TRUE) { do_debug("got answer: %s", resp); } TimerFunc(TIMER_ADDBYTES, len, NULL); number(resp, &nr); if(nr == 480 ) { /* we must do authorization */ retval = do_authenticate(master); if(retval == RETVAL_OK) { /* resend command */ sputline(master->sockfd, cmd, master->do_ssl, master->ssl_struct); if(master->debug == TRUE) { do_debug("sending command: %s", cmd); } len = sgetline(master->sockfd, &resp, master->do_ssl, master->ssl_struct); if( len < 0) { retval = RETVAL_ERROR; } else { number(resp,&nr); if(master->debug == TRUE) { do_debug("got answer: %s", resp); } TimerFunc(TIMER_ADDBYTES, len, NULL); } } } if (good_response != 0 && nr != good_response) { error_log(ERRLOG_REPORT, suck_phrases[29],cmd,resp,NULL); retval = RETVAL_UNEXPECTEDANS; } } if(ret_response != NULL) { *ret_response = resp; } return retval; } /*------------------------------------------------------------------------------*/ /* 1. move N_OLDRC to N_OLD_OLDRC N_OLDRC might not exist */ /* 2. move N_NEWRC to N_OLDRC */ /* 3. rm N_SUPPLEMENTAL */ /*------------------------------------------------------------------------------*/ void do_cleanup(PMaster master) { const char *oldptr; char ptr[PATH_MAX+1]; struct stat buf; int exist; int okay = TRUE; if(master->debug == TRUE) { do_debug("checking for existance of suck.newrc\n"); } if(stat(full_path(FP_GET, FP_TMPDIR, N_NEWRC), &buf) == 0) { if(master->debug == TRUE) { do_debug("found suck.newrc\n"); } /* we do the above test, in case we were in a restart */ /* with -R which would cause no suck.newrc to be created */ /* since message_index() would be skipped */ /* since no suck.newrc, don't move any of the sucknewrcs around */ strcpy(ptr,full_path(FP_GET, FP_DATADIR, N_OLDRC)); /* must strcpy since full path overwrites itself everytime */ oldptr = full_path(FP_GET, FP_DATADIR, N_OLD_OLDRC); /* does the sucknewsrc file exist ? */ exist = TRUE; if(stat(ptr, &buf) != 0 && errno == ENOENT) { exist = FALSE; } if(master->debug == TRUE) { do_debug("sucknewsrc.old = %s sucknewsrc = %s exist = %s\n", oldptr, ptr, true_str(exist)); } if(exist == TRUE && move_file(ptr, oldptr) != 0) { MyPerror(suck_phrases[30]); okay = FALSE; } else if(move_file(full_path(FP_GET, FP_TMPDIR, N_NEWRC), ptr) != 0) { MyPerror(suck_phrases[31]); okay = FALSE; } } else if( errno != ENOENT) { MyPerror(full_path(FP_GET, FP_DATADIR, N_NEWRC)); okay = FALSE; } if(okay == TRUE && unlink(full_path(FP_GET, FP_DATADIR, N_SUPPLEMENTAL)) != 0 && errno != ENOENT) { /* ENOENT is not an error since this file may not always exist */ MyPerror(suck_phrases[33]); } } /*--------------------------------------------------------------------------------------*/ int scan_args(PMaster master, int argc, char *argv[]) { int loop, i, whicharg, arg, retval = RETVAL_OK; for(loop = 0 ; loop < argc && retval == RETVAL_OK ; loop++) { arg = ARG_NO_MATCH; whicharg = -1; if(master->debug == TRUE) { do_debug("Checking arg #%d-%d: '%s'\n", loop, argc, argv[loop]); } if(argv[loop][0] == FILE_CHAR) { /* totally skip these they are processed elsewhere */ continue; } if(argv[loop][0] == '-' && argv[loop][1] == '-') { /* check long args */ for(i = 0 ; i < NR_ARGS && arg == ARG_NO_MATCH ; i++) { if(arglist[i].larg != NULL) { if(strcmp(&argv[loop][2], arglist[i].larg) == 0) { arg = arglist[i].flag; whicharg = i; } } } } else if (argv[loop][0] == '-') { /* check short args */ for(i = 0 ; i < NR_ARGS ; i++) { if(arglist[i].sarg != NULL) { if(strcmp(&argv[loop][1], arglist[i].sarg) == 0) { arg = arglist[i].flag; whicharg = i; } } } } if(arg == ARG_NO_MATCH) { /* we ignore the file_char arg, it is processed elsewhere */ retval = RETVAL_ERROR; error_log(ERRLOG_REPORT, suck_phrases[34], argv[loop], NULL); } else { /* need to check for valid nr of args and feed em down */ if(( loop + arglist[whicharg].nr_params) < argc) { retval = parse_args(master, arg, &(argv[loop+1])); /* skip em so they don't get processed as args */ loop += arglist[whicharg].nr_params; } else { i = (arglist[whicharg].errmsg < 0) ? 34 : arglist[whicharg].errmsg; error_log(ERRLOG_REPORT, suck_phrases[i], NULL); retval = RETVAL_ERROR; } } } return retval; } /*---------------------------------------------------------------------------------------*/ int parse_args(PMaster master, int arg, char *argv[]) { int x, retval = RETVAL_OK; switch(arg) { case ARG_ALWAYS_BATCH: /* if we have downloaded at least one article, then batch up */ /* even on errors */ master->always_batch = TRUE; break; /* batch file implies MultiFile mode */ case ARG_BATCH_INN: master->batch = BATCH_INNXMIT; master->MultiFile = TRUE; master->batchfile = argv[0]; break; case ARG_BATCH_RNEWS: master->batch = BATCH_RNEWS; master->MultiFile = TRUE; master->batchfile = argv[0]; break; case ARG_BATCH_LMOVE: master->batch = BATCH_LMOVE; master->MultiFile = TRUE; master->batchfile = argv[0]; break; case ARG_BATCH_INNFEED: master->batch = BATCH_INNFEED; master->MultiFile = TRUE; master->batchfile = argv[0]; break; case ARG_BATCH_POST_NR: master->batch_post_nr = atoi(argv[0]); /* no break fall thru to the rest of batch_post*/ case ARG_BATCH_POST: master->batch = BATCH_LIHAVE; master->MultiFile = TRUE; master->batchfile = N_POSTFILE; break; case ARG_CLEANUP: /* cleanup option */ master->cleanup = TRUE; break; case ARG_DIR_TEMP: full_path(FP_SET, FP_TMPDIR, argv[0]); break; case ARG_DIR_DATA: full_path(FP_SET, FP_DATADIR, argv[0]); break; case ARG_DIR_MSGS: full_path(FP_SET, FP_MSGDIR, argv[0]); break; case ARG_DEF_ERRLOG: /* use default error log path */ error_log(ERRLOG_SET_FILE, ERROR_LOG, NULL); master->errlog = ERROR_LOG; break; case ARG_HOST: /* host name */ master->host = argv[0]; break; case ARG_NO_POSTFIX: /* kill files don't use the postfix */ master->kill_ignore_postfix = TRUE; break; case ARG_LANGUAGE: /* load language support */ master->phrases = argv[0]; break; case ARG_MULTIFILE: master->MultiFile = TRUE; break; case ARG_POSTFIX: full_path(FP_SET_POSTFIX, FP_NONE, argv[0]); break; case ARG_QUIET: /* don't display BPS and message count */ master->quiet = TRUE; break; case ARG_RNEWSSIZE: master->rnews_size = atol(argv[0]); break; case ARG_DEF_STATLOG: /* use default status log name */ master->status_file_name = STATUS_LOG; break; case ARG_WAIT_SIG: /* wait (sleep) x seconds between every y articles IF we get a signal */ master->sig_pause_time = atoi(argv[0]); master->sig_pause_nrmsgs = atoi(argv[1]); break; case ARG_ACTIVE: master->do_active = TRUE; break; case ARG_RECONNECT: /* how often do we drop and reconnect to battle INNS's LIKE_PULLER=DONT */ master->reconnect_nr = atoi(argv[0]); break; case ARG_DEBUG: /* debug */ master->debug = TRUE; /* now set up our error log and MyPerror reporting so it goes to debug.suck */ error_log(ERRLOG_SET_DEBUG, NULL, NULL); break; case ARG_ERRLOG: /* error log path */ error_log(ERRLOG_SET_FILE, argv[0], NULL); master->errlog = argv[0]; break; case ARG_HISTORY: master->do_chkhistory = FALSE; break; case ARG_KILLFILE: master->do_killfile = FALSE; break; case ARG_KLOG_NONE: master->killfile_log = KILL_LOG_NONE; break; case ARG_KLOG_SHORT: master->killfile_log = KILL_LOG_SHORT; break; case ARG_KLOG_LONG: master->killfile_log = KILL_LOG_LONG; break; case ARG_MODEREADER: /* mode reader */ master->do_modereader = TRUE; break; case ARG_PORTNR: /* override default portnr */ master->portnr = atoi(argv[0]); break; case ARG_PASSWD: /* passwd */ master->passwd = argv[0]; break; case ARG_RESCAN: /* don't rescan on restart */ master->rescan = FALSE; break; case ARG_STATLOG: /* status log path */ master->status_file_name = argv[0]; break; case ARG_USERID: /* userid */ master->userid = argv[0]; break; case ARG_PASSWD_ENV: /* get userid/password from ENV */ master->userid = getenv("NNTP_USER"); master->passwd = getenv("NNTP_PASS"); master->passwd_env = TRUE; break; case ARG_VERSION: /* show version number */ error_log(ERRLOG_REPORT,"Suck version %v1%\n",SUCK_VERSION, NULL); retval = RETVAL_VERNR; /* so we don't do anything else */ break; case ARG_WAIT: /* wait (sleep) x seconds between every y articles */ master->pause_time = atoi(argv[0]); master->pause_nrmsgs = atoi(argv[1]); break; case ARG_LOCALHOST: master->localhost = argv[0]; break; case ARG_NRMODE: master->nrmode = TRUE; break; case ARG_AUTOAUTH: master->auto_auth = TRUE; break; case ARG_NODEDUPE: master->no_dedupe = TRUE; break; case ARG_READACTIVE: master->activefile = argv[0]; break; case ARG_PREBATCH: master->prebatch = TRUE; break; case ARG_SKIP_ON_RESTART: master->skip_on_restart = TRUE; break; case ARG_KLOG_NAME: master->kill_log_name = argv[0]; break; case ARG_USEGUI: master->use_gui = TRUE; break; case ARG_XOVER: master->do_xover = FALSE; break; case ARG_CONN_DEDUPE: master->conn_dedupe = TRUE; break; case ARG_POST_FILTER: master->post_filter = argv[0]; break; case ARG_CONN_ACTIVE: master->conn_active = TRUE; break; case ARG_HIST_FILE: master->history_file = argv[0]; break; case ARG_HEADER_ONLY: master->header_only = TRUE; break; case ARG_ACTIVE_LASTREAD: x = atoi(argv[0]); if(x > 0) { error_log(ERRLOG_REPORT, suck_phrases[66], NULL); } else { master->active_lastread = atoi(argv[0]); } break; case ARG_USEXOVER: master->use_xover = TRUE; break; case ARG_RESETCOUNTER: master->resetcounter = TRUE; break; case ARG_LOW_READ: master->low_read = TRUE; break; case ARG_SHOW_GROUP: master->show_group = TRUE; break; #ifdef TIMEOUT case ARG_TIMEOUT: TimeOut = atoi(argv[0]); break; #endif #ifdef HAVE_LIBSSL case ARG_USE_SSL: master->do_ssl = TRUE; master->portnr = DEFAULT_SSL_PORT; break; case ARG_LOCAL_SSL: master->local_ssl = TRUE; break; #endif } return retval; } /*----------------------------------*/ /* authenticate when receive a 480 */ /*----------------------------------*/ int do_authenticate(PMaster master) { int len, nr, retval = RETVAL_OK; char *resp, buf[MAXLINLEN]; if(master->userid == NULL || master->passwd == NULL) { error_log(ERRLOG_REPORT, suck_phrases[73], NULL); retval = RETVAL_NOAUTH; } else { /* we must do authorization */ sprintf(buf, "AUTHINFO USER %s\r\n", master->userid); if(master->debug == TRUE) { do_debug("sending command: %s", buf); } sputline(master->sockfd, buf, master->do_ssl, master->ssl_struct); len = sgetline(master->sockfd, &resp, master->do_ssl, master->ssl_struct); if( len < 0) { retval = RETVAL_ERROR; } else { if(master->debug == TRUE) { do_debug("got answer: %s", resp); } TimerFunc(TIMER_ADDBYTES, len, NULL); number(resp, &nr); if(nr == 480) { /* this is because of the pipelining code */ /* we get the second need auth */ /* just ignore it and get the next line */ /* which should be the 381 we need */ len = sgetline(master->sockfd, &resp, master->do_ssl, master->ssl_struct); TimerFunc(TIMER_ADDBYTES, len, NULL); number(resp, &nr); } if(nr != 381) { error_log(ERRLOG_REPORT, suck_phrases[27], resp, NULL); retval = RETVAL_NOAUTH; } else { sprintf(buf, "AUTHINFO PASS %s\r\n", master->passwd); sputline(master->sockfd, buf, master->do_ssl, master->ssl_struct); if(master->debug == TRUE) { do_debug("sending command: %s", buf); } len = sgetline(master->sockfd, &resp, master->do_ssl, master->ssl_struct); if(len < 0) { retval = RETVAL_ERROR; } else { if(master->debug == TRUE) { do_debug("got answer: %s", resp); } TimerFunc(TIMER_ADDBYTES, len, NULL); number(resp, &nr); switch(nr) { case 281: /* bingo */ retval = RETVAL_OK; break; case 502: /* permission denied */ retval = RETVAL_NOAUTH; error_log(ERRLOG_REPORT, suck_phrases[28], NULL); break; default: /* wacko error */ error_log(ERRLOG_REPORT, suck_phrases[27], resp, NULL); retval = RETVAL_NOAUTH; break; } } } } } return retval; } /*--------------------------------------------------------------------------------*/ /* THE strings in this routine is the only one not in the arrays, since */ /* we are in the middle of reading the arrays, and they may or may not be valid. */ /*--------------------------------------------------------------------------------*/ void load_phrases(PMaster master) { int error=TRUE; FILE *fpi; char buf[MAXLINLEN]; if(master->phrases != NULL) { if((fpi = fopen(master->phrases, "r")) == NULL) { MyPerror(master->phrases); } else { fgets(buf, MAXLINLEN, fpi); if(strncmp( buf, SUCK_VERSION, strlen(SUCK_VERSION)) != 0) { error_log(ERRLOG_REPORT, "Invalid Phrase File, wrong version\n", NULL); } else if((both_phrases = read_array(fpi, NR_BOTH_PHRASES, TRUE)) != NULL) { read_array(fpi, NR_RPOST_PHRASES, FALSE); /* skip these */ read_array(fpi, NR_TEST_PHRASES, FALSE); if(( suck_phrases = read_array(fpi, NR_SUCK_PHRASES, TRUE)) != NULL && ( timer_phrases= read_array(fpi, NR_TIMER_PHRASES,TRUE)) && ( chkh_phrases= read_array(fpi, NR_CHKH_PHRASES,TRUE)) && (dedupe_phrases= read_array(fpi, NR_DEDUPE_PHRASES,TRUE)) && ( killf_reasons= read_array(fpi, NR_KILLF_REASONS,TRUE)) && ( killf_phrases= read_array(fpi, NR_KILLF_PHRASES,TRUE)) && ( sucku_phrases= read_array(fpi, NR_SUCKU_PHRASES,TRUE)) && (read_array(fpi, NR_LMOVE_PHRASES, FALSE) == NULL) && /* skip this one */ ( active_phrases=read_array(fpi, NR_ACTIVE_PHRASES,TRUE)) && ( batch_phrases= read_array(fpi, NR_BATCH_PHRASES,TRUE)) && ( xover_phrases= read_array(fpi, NR_XOVER_PHRASES,TRUE)) && ( xover_reasons= read_array(fpi, NR_XOVER_REASONS,TRUE))) { error = FALSE; } } } fclose(fpi); if(error == TRUE) { /* reset back to default */ error_log(ERRLOG_REPORT, "Using default Language phrases\n", NULL); both_phrases = default_both_phrases; suck_phrases=default_suck_phrases; timer_phrases=default_timer_phrases; chkh_phrases=default_chkh_phrases; dedupe_phrases=default_dedupe_phrases; killf_reasons=default_killf_reasons; killf_phrases=default_killf_phrases; killp_phrases=default_killp_phrases; sucku_phrases=default_sucku_phrases; active_phrases=default_active_phrases; batch_phrases=default_batch_phrases; xover_phrases=default_xover_phrases; xover_reasons=default_xover_reasons; } } } /*--------------------------------------------------------------------------------*/ void free_phrases(void) { /* free up the memory alloced in load_phrases() */ if(both_phrases != default_both_phrases) { free_array(NR_BOTH_PHRASES, both_phrases); } if(suck_phrases != default_suck_phrases) { free_array(NR_SUCK_PHRASES, suck_phrases); } if(timer_phrases != default_timer_phrases) { free_array(NR_TIMER_PHRASES, timer_phrases); } if(chkh_phrases != default_chkh_phrases) { free_array(NR_CHKH_PHRASES, chkh_phrases); } if(dedupe_phrases != default_dedupe_phrases) { free_array(NR_DEDUPE_PHRASES, dedupe_phrases); } if(killf_reasons != default_killf_reasons) { free_array(NR_KILLF_REASONS, killf_reasons); } if(killf_phrases != default_killf_phrases) { free_array(NR_KILLF_PHRASES, killf_phrases); } if(killp_phrases != default_killp_phrases) { free_array(NR_KILLP_PHRASES, killp_phrases); } if(sucku_phrases != default_sucku_phrases) { free_array(NR_SUCKU_PHRASES, sucku_phrases); } if(active_phrases != default_active_phrases) { free_array(NR_ACTIVE_PHRASES, active_phrases); } if(batch_phrases != default_batch_phrases) { free_array(NR_BATCH_PHRASES, batch_phrases); } if(xover_phrases != default_xover_phrases) { free_array(NR_XOVER_PHRASES, xover_phrases); } if(xover_reasons != default_xover_reasons) { free_array(NR_XOVER_REASONS, xover_reasons); } } suck-4.3.4/suck.h000066400000000000000000000054751333033562000136160ustar00rootroot00000000000000#ifndef _SUCK_SUCK_H #define _SUCK_SUCK_H 1 #include /* for FILE */ #include "suck_config.h" /* Link list structure one for each article */ typedef struct LinkList { struct LinkList *next; char msgnr[MAX_MSGID_LEN]; int groupnr; long nr; long dbnr; char mandatory; char downloaded; char delete; char sentcmd; } List, *PList; /* link list for group names */ typedef struct GroupList { char group[MAX_GRP_LEN]; int nr; struct GroupList *next; } Groups, *PGroups; /* link list for list overview.fmt */ typedef struct XOVERVIEW { struct XOVERVIEW *next; char *header; /* dynamically alloced */ char *field; int fieldlen; int full; } Overview, *POverview; /* Master Structure */ typedef struct { PList head; PList curr; int nritems; int nrgot; int sockfd; int MultiFile; int status_file; int do_killfile; int do_chkhistory; int do_modereader; int always_batch; int cleanup; int batch; int pause_time; int pause_nrmsgs; int sig_pause_time; int sig_pause_nrmsgs; int killfile_log; int debug; int rescan; int quiet; int kill_ignore_postfix; int reconnect_nr; int do_active; int nrmode; int auto_auth; int no_dedupe; int chk_msgid; int prebatch; int skip_on_restart; int use_gui; int do_xover; int conn_dedupe; int conn_active; int header_only; int active_lastread; int use_xover; int resetcounter; int low_read; int show_group; unsigned short int portnr; long rnews_size; FILE *msgs; FILE *innfeed; int db; const char *userid; const char *passwd; const char *host; const char *batchfile; const char *status_file_name; const char *phrases; const char *errlog; const char *localhost; const char *activefile; const char *kill_log_name; const char *post_filter; const char *history_file; PGroups groups; int grpnr; void *killp; void *xoverp; POverview xoverview; int do_ssl; void *ssl_struct; int local_ssl; void *local_ssl_struct; int batch_post_nr; int passwd_env; } Master, *PMaster; int get_a_chunk(PMaster, FILE *); void free_one_node(PList); int send_command(PMaster, const char *, char **, int); int get_message_index(PMaster); int do_one_group(PMaster, char *, char *, FILE *, long, int); const char *build_command(PMaster, const char *, PList); int allocnode(PMaster, char *, int, char *, long); int do_connect(PMaster, int); enum { RETVAL_ERROR = -1, RETVAL_OK = 0, RETVAL_NOARTICLES, RETVAL_UNEXPECTEDANS, RETVAL_VERNR, \ RETVAL_NOAUTH, RETVAL_EMPTYKILL, RETVAL_NOXOVER }; enum { BATCH_FALSE, BATCH_INNXMIT, BATCH_RNEWS, BATCH_LMOVE, BATCH_INNFEED, BATCH_LIHAVE }; /* poss values for batch variable */ enum { MANDATORY_YES = 'M' , MANDATORY_OPTIONAL = 'O'}; /* do we have to download this article */ /* note the MANDATORY_DELETE is used in the dedupe and checkhistory phases to flag em for deletion */ enum {CONNECT_FIRST, CONNECT_AGAIN}; #endif /* _SUCK_SUCK_H */ suck-4.3.4/suck.spec000066400000000000000000000023131333033562000143050ustar00rootroot00000000000000%define version 4.3.4 %define name suck %define release 1 Name: %{name} Summary: suck - download news from remote NNTP server Version: %{version} Release: %{release} Source: http://www.sucknews.org/%{name}-%{version}.tar.gz Packager: Robert Yetman BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot Copyright: Unknown Group: News %description This package contains software for copying news from an NNTP server to your local machine, and copying replies back up to an NNTP server. It works with most standard NNTP servers, including INN, CNEWS, DNEWS, and typhoon. %prep if [ "${RPM_BUILD_ROOT}" != '/' ] ; then rm -rf ${RPM_BUILD_ROOT} ; fi %setup ./configure --prefix=${RPM_BUILD_ROOT}/usr --mandir=${RPM_BUILD_ROOT}%{_mandir} %build make %install make \ install %clean if [ "${RPM_BUILD_ROOT}" != '/' ] ; then rm -rf ${RPM_BUILD_ROOT} ; fi %post echo Please look in %{_docdir}/%{name}-%{version} for example scripts %files %defattr(0644,root,root,0755) %doc README CHANGELOG sample %defattr(-,root,root,0755) %{_bindir}/suck %{_bindir}/rpost %{_bindir}/testhost %{_bindir}/lmove %{_mandir}/man1/suck.1* %{_mandir}/man1/rpost.1* %{_mandir}/man1/testhost.1* %{_mandir}/man1/lmove.1* suck-4.3.4/suck_config.h000066400000000000000000000173211333033562000151340ustar00rootroot00000000000000#ifndef _SUCK_CONFIG_H #define _SUCK_CONFIG_H 1 #define DEFAULT_NNRP_PORT 119 /* port to talk to on remote machine */ #define LOCAL_PORT 119 /* port to talk to on local host */ #define DEFAULT_SSL_PORT 563 /* if using SSL, this is the default port */ #define LOCAL_SSL_PORT 563 /* if using SSL, for local host */ /* Max length of a Message-ID that is possbile */ #define MAX_MSGID_LEN 512 /* Max length of a Group Name */ #define MAX_GRP_LEN 128 /* If you don't EVER plan to use the KILLFILE stuff, comment this out for a slight speed increase */ /* If you want to, or plan to, use the KILLFILE stuff, uncomment this */ #define KILLFILE 1 /* This is the character used in killfiles to tell if we should do a case sensitive or a */ /* case insensitive compare. It can be overridden in a killfile. */ #define KILLFILE_QUOTE '"' /* This is the character that is used as a comment flag in col 0 in killfiles */ #define KILLFILE_COMMENT_CHAR '#' /* This is the character used in killfiles to tell if we should always do a non-regex */ /* compare on the string. It can be overridden in a killfile. */ #define KILLFILE_NONREGEX '%' /* this is the start buffer size for the killfile get body routine */ /* this should be close to the max size of the message you expect to get */ #define KILL_BODY_BUF_SIZE 80960 /* this is the amount of extra memory we grab everytime we need to increase memory */ /* typically for huge articles */ /* WARNING, THIS MUST BE BIGGER THAN MAXLINLEN */ #define KILL_CHUNK_BUF_INCREASE 10240 /* if using -A (scan active file), this is the number I use for lastread when I get */ /* a new group, 0 = all messages, -100 means get last 100 messages */ #define ACTIVE_DEFAULT_LASTREAD -100 #ifdef HAVE_SELECT /* if the system doesn't support select, then you can't use this */ /* if you want to timeout if no read after X seconds define this */ /* as number of seconds before timeout */ /* else, comment it out. If you comment it out, if link goes down */ /* we'll just sit, twiddling our bits, until whenever. */ #define TIMEOUT 90 #endif /* HAVE_SELECT */ /* signal which will interrupt us DON'T USE SIGKILL OR SIGSTOP */ /* if you don't want to be able to abort, and a miniscule speed */ /* increase, comment this out */ /* WARNING: if you plan to use the killprg part of the killfile routines */ /* then leave this in. If you don't, and the child program dies unexpectedly */ /* suck will also die. */ #define MYSIGNAL SIGTERM #define MYSIGNAL2 SIGINT #define PAUSESIGNAL SIGUSR1 /* If you want to have suck check in your history file for articles */ /* that you already have, uncomment this. */ /* If you use DBM, DBZ, or NDBM then you need to edit the Makefile */ /* to show which DB scheme you use. */ #define CHECK_HISTORY 1 /* If your system doesn't like the lock file stuff in suck.c or lmove.c */ /* comment this out */ #define LOCKFILE 1 /* FULL PATH of error log used if -e option specifed to any of the programs */ /* can be overridden at the command line with -E option */ #define ERROR_LOG "./suck.errlog" /* FULL PATH of status messages log if -s option specified to any of the programs */ /* can be overridden at the command line with -S option */ #define STATUS_LOG "/dev/null" /* /dev/null = silent mode */ /* FIRST Character used in args to signify read arguments from a file */ #define FILE_CHAR '@' /* Comment character in arg file, everything after this on a line is ignored */ #define FILE_ARG_COMMENT '#' /* String used in RPOST to search for Duplicate Message-ID responses from server */ #define RPOST_DUPE_STR "Duplicate" #define RPOST_DUPE_STR2 "Already got" /* used by DNews */ /* FILE NAMES */ /* Data files read in */ #define N_OLDRC "sucknewsrc" /* what newsgroups to download */ #define N_KILLFILE "suckkillfile" /* Parameter file for which articles NOT to download via killfiles*/ #define N_SUPPLEMENTAL "suckothermsgs" /* list of article nrs to add to our list to download */ #define N_LMOVE_ACTIVE "active" /* list of active newsgroups for lmove to read */ #define N_ACTIVE_IGNORE "active-ignore" /* list of newsgroups ignored by read local active option */ #define N_LMOVE_CONFIG "lmove-config" /* config file for lmove */ #define N_XOVER "suckxover" /* parameter file for which articles NOT to download via xover */ #define N_NODOWNLOAD "sucknodownload" /* file name for message-ids that I never download */ #define N_PHRASES "/etc/suck/phrases" /* default location for phrase file */ #define HISTORY_FILE "/var/lib/news/history" /* default location for history file */ /* TEMP FILES created */ #define N_NEWRC "suck.newrc" #define N_KILLLOG "suck.killlog" #define N_LOCKFILE "suck.lock" #define N_DBFILE "suck.db" #define N_TMP_EXTENSION ".tmp" /* added to file in process of being downloaded */ #define N_LMOVE_LOCKFILE "lmove.lock" #define N_POSTFILE "suck.post" /* Old copy of N_OLDRC (saved just in case) */ #define N_OLD_OLDRC "sucknewsrc.old" /* Various DIRECTORY PATHS, these can be overriden from command line */ #define N_TMPDIR "." /* location of Temp Files */ #define N_DATADIR "/etc/suck" /* location of Data Files */ #define N_MSGDIR "./Msgs" /*location of articles produced by suck, if multifile option selected */ /* Argument substition strings for rpost */ #define RPOST_FILTER_IN "$$i" #define RPOST_FILTER_OUT "$$o" /* Max nr of args to the rpost filter */ #define RPOST_MAXARGS 128 /* extension added by rpost to the batch file when */ /* a message upload fails and we have to write out */ /* a failed file */ #define RPOST_FAIL_EXT ".fail" /* RNEWS program called by lpost */ #define RNEWS "/usr/bin/rnews" /* character used as a comment in sucknewsrc */ #define SUCKNEWSRC_COMMENT_CHAR '#' /* character used as a separate in the PATH environment variable */ #define PATH_SEPARATOR ':' /* maximum number of args to killprg */ #define KILLPRG_MAXARGS 128 /* length of string sent to killprg (min length must be 8) */ #define KILLPRG_LENGTHLEN 8 /* mode for directory when creating in lmove */ #define LMOVE_DEFAULT_DIR_MODE 0755 /* in octal */ /* longest line possible in a message for lmove */ #define LMOVE_MAXLINLEN 8192 /* mode for active file when rewriting it */ #define LMOVE_ACTIVE_MODE 0755 /* in octal */ /* max number of args for suck to pass to lmove */ #define LMOVE_MAX_ARGS 12 /* Internal buffer size shouldn't need to change */ #define MAXLINLEN 4096 /* Internal buffer size for print_phrases */ #define PHRASES_BLOCK_SIZE 1024 /* Character that is used to separate variables (front and back) in phrases */ #define PHRASES_SEPARATOR '%' #define PHRASES_VAR_CHAR 'v' /* phrase that defines a group name in suck.sorted */ #define RESTART_GROUP "GROUP" /* stuff needed for Perl killfile interpreter */ #define PERL_PACKAGE_SUB "Embed::Persistant::perl_kill" /* stuff needed for Perl rpost interpreter */ #define PERL_RPOST_SUB "Embed::Persistant::perl_rpost" /* stuff needed for PERL xover interpreter */ #define PERL_OVER_SUB "Embed::Persistant::perl_overview" /* subroutine which gets the overview.fmt list */ #define PERL_XOVER_SUB "Embed::Persistant::perl_xover" /* subroutine which gets each xoverview line */ /*------------------------------------------------------------*/ /* the following should be handled by the auto-config stuff */ /* shouldn't need to touch below here */ #ifndef HAVE_MEMMOVE /* if you don't have memmove, try bcopy */ #define memmove(a,b,c) bcopy(b,a,c) #endif #ifndef PATH_MAX /* in case you don't have it */ #ifdef _POSIX_PATH_MAX #define PATH_MAX _POSIX_PATH_MAX #endif #endif #ifndef PATH_MAX #ifdef MAXPATHLEN #define PATH_MAX MAXPATHLEN #endif #endif #ifndef PATH_MAX /* for Solaris */ #ifdef MAXNAMLEN #define PATH_MAX MAXNAMLEN #endif #endif #endif /* _SUCK_CONFIG_H */ suck-4.3.4/suck_phrases.c000066400000000000000000000225171333033562000153320ustar00rootroot00000000000000const char *default_timer_phrases[] = { "Elapsed Time = %v1% mins %v2% seconds\n", /* 0 */ "%v1% Bytes received in %v2% mins %v3% secs, BPS = %v4%\n", "%v1% BPS", "%v1% Bytes received\n" }; const char *default_chkh_phrases[] = { "Can not open %v1%, Skipping\n", /* 0 */ "Processing History File ", "Bogus line, ignoring: %v1%", "Processed history, %v1% dupes removed\n", "Out of Memory, skipping history check", "Processing History Database ", /* 5 */ "Unknown error reading history file %v1%\n", }; const char *default_dedupe_phrases[] = { "Deduping ", /* 0 */ "Out of Memory, skipping Dedupe\n", "Deduped, %v1% items remaining, %v2% dupes removed.\n", }; const char *default_killf_reasons[] = { "No Reason - major screwup", /* 0 */ "Over Hi-Line parameter", "Under Low-Line parameter", "Too many Newsgroups", "Not matched in Group Keep file", "Multiple Matches/Tiebreaker Used", /* 5 */ "Header match:", "Body match:", "Body Size exceeded, size=", "Body Size too small, size=" "Too many Groups in Xref", /* 10 */ }; const char *default_killf_phrases[] = { "Out of memory\n", /* 0 */ "Out of memory, can't use killfiles\n", "Invalid parameter line %v1%\n", "Unable to log killed article\n", "ARTICLE KILLED: group %v1% - %v2% - MsgID %v3%\n", "Out of memory, skipping killfile regex processing\n", /* 5 */ "No character to process for quote, ignoring\n", "Nothing to scan for in header scan, ignoring\n", "Out of memory, skipping killfile header scan processing\n", "Unable to write article to disk\n", "Nothing to scan for in body scan, ignoring\n", /* 10 */ "Invalid regular expression, ignoring, %v1% - %v2%\n" "No character to process for non-regex, ignoring\n", }; const char *default_killp_phrases[] = { "Out of memory processing killprg args", /* 0 */ "Setting up pipes", "Out of memory processing path", "%v1%: Unable to find, sorry", "Unable to write to child process", "Unable to read from child process", /* 5 */ "Process Error", "Child Process died", "Child process closed pipe, waiting for child to end\n", "Child exited with a status of %v1%\n", "Child exited due to signal %v1%\n", /* 10 */ "Child died, cause of death unknown\n", "killprg: Unable to log killed article", "killprg: ARTICLE KILLED: MsgID %v1%\n", "killperl: unable to allocate memory, ignoring\n", "killperl: error parsing program - %v1%\n", /* 15 */ "killperl: evaluation error: %v1%\n", "killperl: invalid number of values returned: %v1%\n", "killperl: ARTICLE KILLED: MsgID %v1%\n", "killperl: Unable to log killed article", }; const char *default_sucku_phrases[] = { "%v1%: Not a directory\n", /* 0 */ "%v1%: Write permission denied\n", "Lock File %v1%, Invalid PID, aborting\n", "Lock File %v1%, stale PID, removed\n", "Lock File %v1%, stale PID, Unable to remove, aborting\n", "Lock file %v1%, PID exists, aborting\n", /* 5 */ "Unable to create Lock File %v1%\n", "Error writing to file %v1%\n", "Error reading from file %v1%\n", "Invalid call to move_file(), notify programer\n" }; const char *default_suck_phrases[] = { "suck: status log", /* 0 */ "Attempting to connect to %v1%\n", "Initiating restart\n", "No articles\n", "Closed connection to %v1%\n", "BPS", /* 5*/ "No local host name\n", "Cleaning up after myself\n", "Skipping Line: %v1%", "Invalid Line: %v1%", "Invalid number for maxread field: %v1% : ignoring\n", /* 10 */ "GROUP <%v1%> not found on host\n", "GROUP <%v1%>, unexpected response, %v2%\n", "%v1% - %v2%...High Article Nr is low, did host reset its counter?\n", "%v1% - limiting # of articles to %v2%\n", "%v1% - %v2% articles %v3%-%v4%\n", /* 15 */ "%v1% Articles to download\n", "Processing Supplemental List\n", "Supplemental Invalid Line: %v1%, ignoring\n", "Supplemental List Processed, %v1% articles added, %v2% articles to download\n", "Total articles to download: %v1%\n", /* 20 */ "Invalid Article Nr, skipping: %v1%\n", "Out of Memory, aborting\n", "Unable to write db record to disk, aborting\n", "Signal received, will finish downloading article.\n", "Notify programmer, problem with pause_signal()\n", /* 25 */ "\nGot Pause Signal, swapping pause values\n", "Weird Response to Authorization: %v1%", "Authorization Denied", "***Unexpected response to command, %v1%\n%v2%\n", "Moving newsrc to backup", /* 30 */ "Moving newrc to newsrc", "Removing Suck DB File", "Removing supplemental article file", "Invalid argument: %v1%\n", "No rnews batch file size provided\n", /* 35 */ "No postfix provided\n", "No directory name provided\n", "Invalid directory\n", "Invalid Batch arg: %v1%\n", "No Batch file name provided\n", /* 40 */ "No Error Log name provided\n", "No Status Log name provided\n", "No Userid provided\n", "No Passwd provided\n", "No Port Number provided\n", /* 45 */ "Invalid pause arguments\n", "No phrase file provided\n", "GROUP command not recognized, try the -M option\n", "No number of reconnects provided\n", "No host to post to provided\n", /* 50 */ "No host name specified\n", "No timeout value specified\n", "No Article Number provided, can't use MsgNr mode\n", "Switching to MsgID mode\n", "Userid or Password not provided, unable to auto-authenticate\n", /* 55 */ "No local active file provided\n", "Error writing article to file\n", "Can't pre-batch files, no batch mode specified\n", "Broken connection, aborting\n", "Restart: Skipping first article\n", /*60*/ "No kill log file name provided\n", "No post filter provided\n", "-%v1%-%v2%-Message-ID too long, ignoring\n", "No history file path provided\n", "No default read for new active groups provided\n", /* 65 */ "Invalid default read for new active group, must be <= 0\n", "Error setting up signal handlers", "Processing Nodownload File\n", "Invalid line in Nodownload File %v1%, ignoring\n", "Nodownload File processed, %v1% lines read, %v2% articles deleted, %v3% articles remaining\n", /* 70 */ "%v1% - %v2%... High Number article is low, resetting last read to %v3%\n", "No batch_post count provide\n", "Userid or Password not provided, unable to authenticate\n", "No host name on command line, aborting\n", }; const char *default_batch_phrases[] = { "Calling lmove\n", /* 0 */ "Forking lmove\n", "Running lmove\n", "Building INN Batch File\n", "Building RNews Batch File(s)\n", "Posting Messages to %v1%\n", /* 5 */ "No batchfile to process\n", "Invalid batchfile line %v1%, ignoring", "Can't post: %v1% : reason : %v2%", "Error posting: %v1% : reason : %v2%", "%v1% Messages Posted\n", /* 10 */ "Article Not Wanted, deleting: %v1% - %v2%", "Article Rejected, deleting: %v1% - %v2%", }; const char *default_active_phrases[] = { "Unable to get active list from local host\n", /* 0 */ "Out of memory reading local list\n", "Loading active file from %v1%\n", "Invalid group line in sucknewsrc, ignoring: %v1%\n", "Deleted group in sucknewsrc, ignoring: %v1%", "%v1% Articles to download\n", /* 5 */ "No sucknewsrc to read, creating\n", "Out of memory reading ignore list, skipping\n", "Adding new groups from local active file to sucknewsrc\n", "Reading current sucknewsrc\n", "New Group - adding to sucknewsrc: %v1%\n", /* 10 */ "Unable to open %v1%\n", "Active-ignore - error in expression %v1% - %v2%\n", }; const char *default_xover_phrases[] = { "Unexpected XOVER command error, aborting: %v1%", /* 0 */ "Xover - Invalid Article Number - %v1%", "Xover - Invalid Subject or early termination of line - %v1%", "Xover - Invalid Author or early termination of line - %v1%", "Xover - Invalid Date or early termination of line - %v1%", "Xover - Invalid Article-ID or early termination of line - %v1%", /* 5 */ "Xover - Invalid Reference or early termination of line - %v1%", "Xover - Invalid Byte Count - %v1%", "Xover - Invalid Line Count - %v1%", "ARTICLE KILLED XOVER: group %v1% - %v2% - MsgID %v3%\n", "Xover=%v1%\n", /* 10 */ "Unable to log Xover-killed article", "Out of memory, unable to process Xover killfiles", "No Message-ID in XOVER-%v1%- skipping\n", }; const char *default_xover_reasons[] = { "Xover - Article too big", /* 0 */ "Xover - Article too small", "Xover - Too many lines", "Xover - Too few lines", "Xover - Match on Header", "Xover - No Match on Group Keep file", /* 5 */ "Xover - Multiple Matches/Tiebreaker used", "Xover - Too many Groups in Xref", "Xover - Match by external program", "Xover - Match by perl program" }; int nr_suck_phrases= sizeof(default_suck_phrases)/sizeof(default_suck_phrases[0]); int nr_timer_phrases= sizeof(default_timer_phrases)/sizeof(default_timer_phrases[0]); int nr_chkh_phrases= sizeof(default_chkh_phrases)/sizeof(default_chkh_phrases[0]); int nr_dedupe_phrases= sizeof(default_dedupe_phrases)/sizeof(default_dedupe_phrases[0]); int nr_killf_reasons= sizeof(default_killf_reasons)/sizeof(default_killf_reasons[0]); int nr_killf_phrases= sizeof(default_killf_phrases)/sizeof(default_killf_phrases[0]); int nr_killp_phrases= sizeof(default_killp_phrases)/sizeof(default_killp_phrases[0]); int nr_sucku_phrases= sizeof(default_sucku_phrases)/sizeof(default_sucku_phrases[0]); int nr_active_phrases=sizeof(default_active_phrases)/sizeof(default_active_phrases[0]); int nr_batch_phrases=sizeof(default_batch_phrases)/sizeof(default_batch_phrases[0]); int nr_xover_phrases=sizeof(default_xover_phrases)/sizeof(default_xover_phrases[0]); int nr_xover_reasons=sizeof(default_xover_reasons)/sizeof(default_xover_reasons[0]); suck-4.3.4/suckutils.c000066400000000000000000000204411333033562000146600ustar00rootroot00000000000000#include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #ifdef HAVE_DIRENT_H # include #else # define dirent direct # ifdef HAVE_SYS_NDIR_H # include # endif # ifdef HAVE_SYS_DIR_H # include # endif #endif #ifdef HAVE_LIMITS_H #include #endif #include #include #include #include #ifdef DMALLOC #include #endif #include "suck_config.h" #include "suck.h" #include "both.h" #include "suckutils.h" #include "phrases.h" /*------------------------------------------------------------------------*/ /* check if directory exists, if not, try to create it. */ /* return TRUE if made/exists and can write to it */ /* -----------------------------------------------------------------------*/ int checkdir(const char *dirname) { struct stat buf; int retval = TRUE; if(stat(dirname, &buf)== 0) { /* it exists */ if(!S_ISDIR(buf.st_mode)) { error_log(ERRLOG_REPORT, sucku_phrases[0], dirname, NULL); retval = FALSE; } else if(access(dirname, W_OK) != 0) { error_log(ERRLOG_REPORT, sucku_phrases[1], dirname, NULL); retval = FALSE; } } else if(errno == ENOENT) { /* doesn't exist, make it */ if(mkdir(dirname, (S_IRUSR | S_IWUSR | S_IXUSR)) != 0) { MyPerror(dirname); retval = FALSE; } } else { /* huh? */ MyPerror(dirname); retval = FALSE; } return retval; } /*--------------------------------------------------------------*/ /* if which = FP_SET, store directory in array */ /* = FP_GET, retrieve directory and add file name */ /* = FP_GET_NOPOSTFIX, get without appending postfix */ /* = FP_GET_POSTFIX, get ONLY the postfix */ /* retval FP_SET, if valid dir, the dir, else NULL */ /* FP_GET, full path */ /* FP_GET_POSTFIX, only the postfix */ /*--------------------------------------------------------------*/ const char *full_path(int which, int dir, const char *fname) { static char dirs[FP_NR_DIRS][PATH_MAX+1] = { N_TMPDIR, N_DATADIR, N_MSGDIR }; static char path[PATH_MAX+1]; /* static so area valid after return */ static char postfix[PATH_MAX+1] = { "\0" }; char *retptr; int ok, i; retptr = NULL; switch(which) { case FP_GET: case FP_GET_NOPOSTFIX: /* put the right directory as the first part of the path */ strcpy(path, dirs[dir]); /* if it contains a dit, then change to full path */ if(path[0] == '.') { path[0] = '\0'; if(getcwd(path, PATH_MAX) == NULL) { MyPerror(path); /* restore current path and pray */ /* it will work later */ strcpy(path, dirs[dir]); } else { /* tack on remainder of path */ /* the 1 so skip . */ strcat(path, &dirs[dir][1]); } } if(fname != NULL && fname[0] != '\0') { i = strlen(path); /* if nothing to put on don't do any of this */ /* put on trailing slant bar if needed */ if(path[i-1] != '/') { path[i++] = '/'; path[i] = '\0'; /* null terminate it */ } /* finally, put on filename */ strcpy(&path[i], fname); if(which != FP_GET_NOPOSTFIX && postfix[0] != '\0') { strcat(path, postfix); } } retptr = path; break; case FP_SET_POSTFIX: ok = TRUE; strncpy(postfix, fname, sizeof(postfix)); break; case FP_GET_POSTFIX: retptr = postfix; break; case FP_SET: ok = TRUE; switch(dir) { case FP_TMPDIR: if(access(fname, W_OK) == -1) { MyPerror(fname); ok = FALSE; } break; case FP_DATADIR: if(access(fname, R_OK) == -1) { MyPerror(fname); ok = FALSE; } break; case FP_MSGDIR: /* do_nothing, this is checked elsewhere */ break; } if(ok == FALSE) { retptr = NULL; } else { strncpy(dirs[dir], fname, sizeof(dirs[0])); retptr = dirs[dir]; } break; } return retptr; } #ifdef LOCKFILE /*--------------------------------------------------------------*/ int do_lockfile(PMaster master) { int retval; FILE *f_lock; const char *lockfile; pid_t pid; retval = RETVAL_OK; lockfile = full_path(FP_GET, FP_TMPDIR, N_LOCKFILE); if((f_lock = fopen(lockfile, "r")) != NULL) { /* okay, let's try and see if this sucker is truly alive */ long tmp = 0; fscanf(f_lock, "%ld", &tmp); fclose(f_lock); pid = (pid_t)tmp; if(pid <= 0) { error_log(ERRLOG_REPORT, sucku_phrases[2], lockfile, NULL); retval = RETVAL_ERROR; } /* this next technique borrowed from pppd, sys-linux.c (ppp2.2.0c) */ /* if the pid doesn't exist (can't send any signal to it), then try */ /* to remove the stale PID file so can recreate it. */ else if(kill(pid, 0) == -1 && errno == ESRCH) { /* no pid found */ if(unlink(lockfile) == 0) { /* this isn't error so don't put in error log */ print_phrases(master->msgs, sucku_phrases[3], lockfile, NULL); } else { error_log(ERRLOG_REPORT, sucku_phrases[4], lockfile, NULL); retval = RETVAL_ERROR; } } else { error_log(ERRLOG_REPORT, sucku_phrases[5], lockfile, NULL); retval = RETVAL_ERROR; } } if(retval == RETVAL_OK) { if((f_lock = fopen(lockfile, "w")) != NULL) { fprintf(f_lock, "%ld", (long) getpid()); fclose(f_lock); } else { error_log(ERRLOG_REPORT, sucku_phrases[6], lockfile, NULL); retval = RETVAL_ERROR; } } return retval; } #endif /*-------------------------------------------------------------------------*/ int qcmp_msgid(const char *nr1, const char *nr2) { /* return -1 if a < b, 0 if a == b, 1 if a > b */ int retval = 0; /* cmp two article id numbers */ /* which is */ /* use case sensitive compares up to the @ */ /* then use case insensitive compares on the organization */ /* this is to conform to rfc822 */ int retval = FALSE; if(nr1 != NULL && nr2 != NULL) { while ( *nr1 == *nr2 && *nr1 != '\0' && *nr1 != '@') { nr1++; nr2++; } if(*nr1 == '@') { while( tolower(*nr1) == tolower(*nr2) && *nr1 != '\0') { nr1++; nr2++; } } if(*nr1 == *nr2 && *nr1 == '\0') { retval = TRUE; } } return retval; } /*-----------------------------------------------------------------------------*/ /* move a file from one location to another */ /*-----------------------------------------------------------------------------*/ int move_file(const char *src, const char *dest) { char block[MAXLINLEN]; FILE *fpi, *fpo; int retval = RETVAL_OK; size_t nrread; /* first try the easy way */ if(src == NULL || dest == NULL) { error_log(ERRLOG_REPORT, sucku_phrases[9], NULL); retval = RETVAL_ERROR; } else if(rename(src, dest) != 0) { #ifdef EMX if(errno != EACCES) { MyPerror(src); retval = RETVAL_ERROR; } #else if(errno != EXDEV) { MyPerror(src); retval = RETVAL_ERROR; } #endif else { /* can't rename, try to copy file */ if((fpi = fopen(src, "r")) == NULL) { MyPerror(src); retval = RETVAL_ERROR; } else if(( fpo = fopen(dest, "w")) == NULL) { fclose(fpi); MyPerror(dest); retval = RETVAL_ERROR; } else { while(retval == RETVAL_OK && (nrread = fread(block, sizeof(char), MAXLINLEN, fpi)) > 0) { if(fwrite(block, sizeof(char), nrread, fpo) != nrread) { error_log(ERRLOG_REPORT, sucku_phrases[7], dest, NULL); retval = RETVAL_ERROR; } } if(ferror(fpi) != 0) { error_log(ERRLOG_REPORT, sucku_phrases[8], src, NULL) ; } fclose(fpi); fclose(fpo); } /* now that copy is successful, nuke it */ if(retval == RETVAL_OK) { unlink(src); } } } return retval; } suck-4.3.4/suckutils.h000066400000000000000000000011311333033562000146600ustar00rootroot00000000000000#ifndef _SUCK_SUCKUTILS_H #define _SUCK_SUCKUTILS_H 1 int checkdir(const char *); const char *full_path(int, int, const char *); int qcmp_msgid(const char *, const char *); int cmp_msgid(const char *, const char *); int move_file(const char *, const char *); #ifdef LOCKFILE int do_lockfile(PMaster); #endif enum { FP_SET, FP_GET, FP_SET_POSTFIX, FP_GET_NOPOSTFIX, FP_GET_POSTFIX}; /* get or set call for full_path() */ enum { FP_TMPDIR, FP_DATADIR, FP_MSGDIR, FP_NONE }; /* which dir in full_path() */ #define FP_NR_DIRS 3 /* this must match nr of enums */ #endif /* _SUCK_SUCKUTILS_H */ suck-4.3.4/test_phrases.c000066400000000000000000000011351333033562000153350ustar00rootroot00000000000000const char *default_test_phrases[] = { "Need date time args (YYMMDD HHMMSS)\n", /* 0 */ "Invalid date time args %v1% %v2%\n", "No Error Log name provided\n", "testhost: Status Log", "No port number provided\n", "No Userid Specified\n", /* 5 */ "No Password Specified\n", "Invalid argument:%v1%\n", "Error talking to host %v1%\n", "Weird Response to Authorization: %v1%\n", "Authorization Denied\n", /* 10 */ "No Phrases file provided\n", "No Status Log name provided\n", "No timeout value provided\n", }; int nr_test_phrases = sizeof(default_test_phrases)/sizeof(default_test_phrases[0]); suck-4.3.4/testhost.c000066400000000000000000000262351333033562000145160ustar00rootroot00000000000000#include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_DIRENT_H # include #else # define dirent direct # ifdef HAVE_SYS_NDIR_H # include # endif # ifdef HAVE_SYS_DIR_H # include # endif #endif #include #include #include #include #include #include #ifdef DMALLOC #include #endif #include "both.h" #include "suck_config.h" #include "phrases.h" /* set up defaults */ char **test_phrases=default_test_phrases; char **both_phrases=default_both_phrases; #define MAXCMDLEN 128 /* length of longest command to send + its args */ struct { const char *command; int response; } cmds[] = { { "help", 100 }, { "list", 215 }, { "list newsgroups", 215 }, { "newgroups", 231}, { "list overview.fmt", 215}, }; enum { CMD_HELP, CMD_LIST, CMD_DESC, CMD_NEWGRP, CMD_OVERVIEW }; enum { RETVAL_OK = 0, RETVAL_ERROR = -1, RETVAL_BAD_DATES = -2, RETVAL_NOAUTH = -3}; /*-functions----*/ int do_a_command(int, int, FILE *, char *, char *, char *, int, void *); int check_date_format(char *, char *, char *); void load_phrases(const char *); void free_phrases(void); /*------------------------------------------------------------------*/ int main(int argc, char *argv[]) { int sockfd, response, loop, cmd, quiet, mode_reader, do_ssl, retval = RETVAL_OK; struct stat sbuf; unsigned short int portnr; FILE *fptr = stdout; /* used to print output to */ void *ssl_struct; char *host, *buf, *ptr, *cmdargs, *userid, *passwd; char ndate[14]; /* 6 for yymmdd 6 for hhmmss and 1 for space and 1 for null */ const char *phrases; /* set up defaults */ host = getenv("NNTPSERVER"); cmd = CMD_HELP; loop = 1; cmdargs = NULL; portnr = DEFAULT_NNRP_PORT; passwd = NULL; userid = NULL; phrases = NULL; mode_reader = FALSE; do_ssl = FALSE; quiet = FALSE; /* do we show status or not */ /* have to do this next so if set on cmd line, overrides this */ #ifdef N_PHRASES /* in case someone nukes def */ if(stat(N_PHRASES, &sbuf) == 0 && S_ISREG(sbuf.st_mode)) { /* we have a regular phrases file make it the default */ phrases = N_PHRASES; } #endif if(argc > 1 && argv[loop][0] != '-') { host = argv[loop++]; } for(;loop argc) { error_log(ERRLOG_REPORT, test_phrases[0], NULL); retval = RETVAL_ERROR; } else { if((retval = check_date_format(ndate, argv[loop+1], argv[loop+2])) == RETVAL_OK) { cmdargs = ndate; } else { error_log(ERRLOG_REPORT, test_phrases[1], argv[loop+1], argv[loop+2], NULL); } loop += 2; /* past the args if there, if not this will finish up the arg loop */ } break; case 'e': /* use default error log path */ error_log(ERRLOG_SET_FILE, ERROR_LOG, NULL); break; case 'E': /* error log path */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, test_phrases[2], NULL); retval = RETVAL_ERROR; } else { error_log(ERRLOG_SET_FILE, argv[++loop], NULL); } break; case 's': /* use default status log path */ if((fptr = fopen(STATUS_LOG, "a")) == NULL) { MyPerror(test_phrases[3]); retval = RETVAL_ERROR; } break; case 'S': /* status log path */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, test_phrases[12], NULL); retval = RETVAL_ERROR; } else if((fptr = fopen(argv[++loop], "a")) == NULL) { MyPerror(test_phrases[3]); retval = RETVAL_ERROR; } break; case 'N': /* override port nr */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, test_phrases[4], NULL); retval = RETVAL_ERROR; } else { portnr = atoi(argv[++loop]); } break; case 'U': /* next arg is userid for authorization */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, test_phrases[5], NULL); retval = RETVAL_ERROR; } else { userid = argv[++loop]; } break; case 'P': /* next arg is password for authorization */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, test_phrases[6], NULL); retval = RETVAL_ERROR; } else { passwd = argv[++loop]; } break; case 'Q': /* get from env */ userid = getenv("NNTP_USER"); passwd = getenv("NNTP_PASS"); break; case 'l': /* next arg is phrases file */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, test_phrases[11], NULL); retval = RETVAL_ERROR; } else { phrases = argv[++loop]; } break; #ifdef TIMEOUT case 'T': /* timeout */ if(loop+1 == argc) { error_log(ERRLOG_REPORT, test_phrases[13], NULL); retval = RETVAL_ERROR; } else { TimeOut = atoi(argv[++loop]); } break; #endif #ifdef HAVE_LIBSSL case 'z': do_ssl = TRUE; portnr = DEFAULT_SSL_PORT; break; #endif default: retval = RETVAL_ERROR; error_log(ERRLOG_REPORT, test_phrases[7], argv[loop], NULL); } } else { retval = RETVAL_ERROR; error_log(ERRLOG_REPORT, test_phrases[7], argv[loop], NULL); } } if(retval == RETVAL_OK) { load_phrases(phrases); /* this is here so everything displays okay */ sockfd = connect_to_nntphost( host, NULL, 0, (quiet == FALSE)? fptr : NULL, portnr, do_ssl, &ssl_struct); if(sockfd < 0 ) { retval = RETVAL_ERROR; } /* get the announcement line */ else if(sgetline(sockfd, &buf, do_ssl, ssl_struct) < 0) { retval = RETVAL_ERROR; } else { ptr = number(buf, &response); if(quiet == FALSE) { fprintf(fptr,"%s",ptr); /* print it */ } if(response != 200 && response != 201) { /* not a valid response */ retval = RETVAL_ERROR; } else { if(mode_reader == TRUE) { sputline(sockfd, "mode reader\r\n", do_ssl, ssl_struct); sgetline(sockfd, &buf, do_ssl, ssl_struct); } do_a_command(sockfd, cmd, fptr, cmdargs, userid, passwd, do_ssl, ssl_struct); } } if(sockfd >= 0) { disconnect_from_nntphost(sockfd, do_ssl, &ssl_struct); } if(retval == RETVAL_ERROR) { error_log(ERRLOG_REPORT, test_phrases[8], host, NULL); } } if(fptr != stdout && fptr != NULL) { fclose(fptr); } free_phrases(); /* do this last so everything is displayed correctly */ exit(retval); } /*-------------------------------------------------------------------------------*/ int do_a_command(int sockfd, int cmd, FILE *fptr, char *cmdargs, char *userid, char *passwd, int do_ssl, void *ssl_struct ) { char *ptr, *buf, cmdstr[MAXCMDLEN], buf2[MAXCMDLEN]; int response; int len, done, retval = RETVAL_OK; /* build command to send */ strcpy(cmdstr, cmds[cmd].command); if(cmdargs != NULL) { strcat(cmdstr, " "); strcat(cmdstr, cmdargs); } strcat(cmdstr, "\r\n"); sputline(sockfd, cmdstr, do_ssl, ssl_struct); /* which command do I run? */ sgetline(sockfd, &buf, do_ssl, ssl_struct); /* get response */ ptr = number(buf, &response); if(response == 480 ) { /* we must do authorization */ sprintf(buf2, "AUTHINFO USER %s\r\n", userid); sputline(sockfd, buf2, do_ssl, ssl_struct); len = sgetline(sockfd, &buf, do_ssl, ssl_struct); if( len < 0) { retval = RETVAL_ERROR; } else { ptr = number(buf, &response); if(response != 381) { error_log(ERRLOG_REPORT, test_phrases[9], buf, NULL); retval = RETVAL_NOAUTH; } else { sprintf(buf2, "AUTHINFO PASS %s\r\n", passwd); sputline(sockfd, buf2, do_ssl, ssl_struct); len = sgetline(sockfd, &buf, do_ssl, ssl_struct); if(len < 0) { retval = RETVAL_ERROR; } else { number(buf, &response); switch(response) { case 281: /* bingo resend original command*/ sputline(sockfd, cmdstr, do_ssl, ssl_struct); len = sgetline(sockfd, &buf, do_ssl, ssl_struct); if( len < 0) { retval = RETVAL_ERROR; } else { ptr = number(buf,&response); } break; case 502: /* permission denied */ retval = RETVAL_NOAUTH; error_log(ERRLOG_REPORT, test_phrases[10], NULL); break; default: /* wacko error */ error_log(ERRLOG_REPORT, test_phrases[9], ptr, NULL); retval = RETVAL_NOAUTH; break; } } } } } if(cmds[cmd].response != response) { error_log(ERRLOG_REPORT, "%v1%\n", buf, NULL); retval = RETVAL_ERROR; } else { /* okay here we go */ fprintf(fptr, "%s", ptr); /* commands are ended by a . on a line by itself */ len = done = 0; while( len >=0 && done == 0) { len = sgetline(sockfd, &buf, do_ssl, ssl_struct); if(len == 2 && strcmp(buf, ".\n") == 0) { done = 1; } else if(len > 0) { fprintf(fptr,"%s",buf); } } if(len < 0) { retval = RETVAL_ERROR; } } return retval; } /*---------------------------------------------------------------*/ int check_date_format(char *dates, char *indate, char *intime) { /* if indate & intime are not valid format, return error */ /* when done, dates will be yymmdd hhmmss */ int i, retval = RETVAL_OK; /* now test my incoming args */ if(indate == NULL || intime == NULL) { retval = RETVAL_ERROR; } else if(strlen(indate) != 6 || strlen(intime) != 6) { retval = RETVAL_ERROR; } else { for(i=0;i<6;i++) { if(!isdigit(indate[i]) || !isdigit(intime[i])) { retval = RETVAL_ERROR; } } } if(retval == RETVAL_OK) { sprintf(dates, "%s %s", indate, intime); } return retval; } /*--------------------------------------------------------------------------------*/ /* THE strings in this routine is the only one not in the arrays, since */ /* we are in the middle of reading the arrays, and they may or may not be valid. */ /*--------------------------------------------------------------------------------*/ void load_phrases(const char *phrases) { int error=TRUE; FILE *fpi; char buf[MAXLINLEN]; if(phrases != NULL) { if((fpi = fopen(phrases, "r")) == NULL) { MyPerror(phrases); } else { fgets(buf, MAXLINLEN, fpi); if(strncmp( buf, SUCK_VERSION, strlen(SUCK_VERSION)) != 0) { error_log(ERRLOG_REPORT, "Invalid Phrase File, wrong version\n", NULL); } else if((both_phrases = read_array(fpi, NR_BOTH_PHRASES, TRUE)) != NULL) { read_array(fpi, NR_RPOST_PHRASES, FALSE); /* skip these */ if((test_phrases = read_array(fpi, NR_TEST_PHRASES, TRUE)) != NULL) { error = FALSE; } } } fclose(fpi); if(error == TRUE) { /* reset back to default */ error_log(ERRLOG_REPORT, "Using default Language phrases\n", NULL); test_phrases = default_test_phrases; both_phrases = default_both_phrases; } } } /*--------------------------------------------------------------------------------*/ void free_phrases(void) { /* free up the memory alloced in load_phrases() */ if(test_phrases != default_test_phrases) { free_array(NR_TEST_PHRASES, test_phrases); } if(both_phrases != default_both_phrases) { free_array(NR_BOTH_PHRASES, both_phrases); } } suck-4.3.4/timer.c000066400000000000000000000057121333033562000137560ustar00rootroot00000000000000#include #include #include "suck_config.h" #include "timer.h" #include "phrases.h" #include "both.h" #ifdef TIMEOUT # if TIME_WITH_SYS_TIME # include # include # else # if HAVE_SYS_TIME_H # include # else # include # endif # endif #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef DMALLOC #include #endif /*internal functions */ #ifdef HAVE_GETTIMEOFDAY double get_elapsed(struct timeval *); #endif /*-----------------------------------------------------------*/ #ifdef HAVE_GETTIMEOFDAY double TimerFunc(int which_function, long nradd, FILE *fpi) { #else void TimerFunc(int which_function, long nradd, FILE *fpi) { #endif #ifdef HAVE_GETTIMEOFDAY static struct timeval start; char strmins[32], strsecs[32], strbps[32]; #endif static long nrbytes = 0L; /* just in case */ char strbytes[32]; #ifdef HAVE_GETTIMEOFDAY double elapsed, bps, retval = 0.0; long mins; /* long so can get mins to exact decimal */ #endif switch(which_function) { case TIMER_START: nrbytes = 0L; #ifdef HAVE_GETTIMEOFDAY gettimeofday(&start, NULL); #endif break; case TIMER_ADDBYTES: nrbytes += nradd; break; case TIMER_GET_BPS: elapsed = get_elapsed(&start); retval = (elapsed > 0.0) ? nrbytes / elapsed : 0.0; break; case TIMER_DISPLAY: #ifdef HAVE_GETTIMEOFDAY if(nrbytes > 0) { elapsed = get_elapsed(&start); bps = (elapsed > 0.0) ? nrbytes / elapsed : 0.0; sprintf(strbps,"%.1f", bps); print_phrases(fpi, timer_phrases[2], strbps, NULL); } #endif break; case TIMER_TIMEONLY: #ifdef HAVE_GETTIMEOFDAY elapsed = get_elapsed(&start); mins = ((long) elapsed) / 60 ; /* get minutes */ elapsed -= (mins * 60); /* subtract to get remainder */ sprintf(strmins, "%ld", mins); sprintf(strsecs, "%.2f", elapsed); print_phrases(fpi, timer_phrases[0], strmins, strsecs, NULL); #endif break; case TIMER_TOTALS: sprintf(strbytes, "%ld", nrbytes); #ifdef HAVE_GETTIMEOFDAY elapsed = get_elapsed(&start); bps = (elapsed > 0.0 && nrbytes > 0) ? nrbytes / elapsed : 0.0; mins = ((long) elapsed) / 60 ; /* get minutes */ elapsed -= (mins * 60); /* subtract to get remainder */ sprintf(strmins, "%ld", mins); sprintf(strsecs, "%.2f", elapsed); sprintf(strbps, "%.1f", bps); print_phrases(fpi, timer_phrases[1], strbytes, strmins, strsecs, strbps, NULL); #else print_phrases(fpi, timer_phrases[3], strbytes, NULL); #endif break; default: /* ignore invalid commands */ break; } #ifdef HAVE_GETTIMEOFDAY return retval; #else return; #endif } /*-----------------------------------------------------------------------------------*/ #ifdef HAVE_GETTIMEOFDAY double get_elapsed(struct timeval *start) { struct timeval curr; double elapsed; /* compute elapsed time, in seconds */ gettimeofday(&curr, NULL); elapsed = curr.tv_sec - start->tv_sec; elapsed += (((double) (curr.tv_usec - start->tv_usec)) / 1000000.0); return elapsed; } #endif suck-4.3.4/timer.h000066400000000000000000000004511333033562000137560ustar00rootroot00000000000000#ifndef _SUCK_TIMER_H #define _SUCK_TIMER_H 1 #ifdef HAVE_GETTIMEOFDAY double TimerFunc(int, long, FILE *); #else void TimerFunc(int, long, FILE *); #endif enum timer_funcs { TIMER_START, TIMER_ADDBYTES, TIMER_DISPLAY, TIMER_TOTALS, TIMER_TIMEONLY, TIMER_GET_BPS }; #endif /* _SUCK_TIMER_H */ suck-4.3.4/xover.c000066400000000000000000000411061333033562000137760ustar00rootroot00000000000000#include #include #include #ifdef HAVE_LIMITS_H #include #endif #ifdef DMALLOC #include #endif #include "suck_config.h" #include "suck.h" #include "both.h" #include "xover.h" #include "phrases.h" #include "killfile.h" #include "suckutils.h" #ifdef EMX #define strcasecmp(x,y) strcmp((x),(y)) #endif typedef struct Grplist { PGroup grp; struct Grplist *next; } GrpList, *PGrpList; /*func prototypes */ int do_one_line(PMaster, char *, char *,PGrpList, int); int chk_a_group(PMaster, POneKill, POverview, char **); int match_group(char *, char *, int); char *find_msgid(PMaster, char *); /* these MUST match xover_reasons[] ! EXCEPT FOR THE last entry which MUST BE REASON_NONE */ enum { REASON_HIBYTES, REASON_LOWBYTES, REASON_HILINES, REASON_LOWLINES, REASON_HEADER, REASON_NOKEEP, REASON_TIE,\ REASON_XREF, REASON_PRG, REASON_PERL, REASON_NONE}; /*----------------------------------------------------------------------*/ int do_group_xover(PMaster master, char *grpname, long startnr, long endnr) { int done, len, nr, retval = RETVAL_OK; char *resp, cmd[MAXLINLEN]; PKillStruct xoverkill; PGroup grpkill = NULL; PGrpList glist = NULL, gat = NULL, gptr; sprintf(cmd, "xover %ld-%ld\r\n", startnr,endnr); retval = send_command(master, cmd, &resp, 0); if(retval == RETVAL_OK) { number(resp, &nr); switch(nr) { default: case 412: case 502: /* abort on these errors */ error_log(ERRLOG_REPORT, xover_phrases[0], resp, NULL); retval = RETVAL_NOXOVER; break; case 420: /* no articles available, no prob */ break; case 224: /* first, check to see if there are group kill file(s) for this group */ /* we do this here, so we only do it once per group, since we don't */ /* have all the crossposted groups to deal with */ xoverkill = master->xoverp; if(xoverkill->totgrps > 0) { for(nr = 0; xoverkill != NULL && nr < xoverkill->totgrps; nr++) { if(match_group(xoverkill->grps[nr].group, grpname, master->debug) == TRUE) { grpkill = &(xoverkill->grps[nr]); if(master->debug == TRUE) { do_debug("Using Group Xover killfile %s\n", grpkill->group); } /* now add it to our list to pass down to do_one_line()*/ if((gptr = calloc(1, sizeof(GrpList))) == NULL) { error_log(ERRLOG_REPORT, xover_phrases[12], NULL); retval = RETVAL_ERROR; } else { gptr->grp = grpkill; gptr->next = NULL; /* add to list */ if(glist == NULL) { /* head of list */ glist = gptr; } else { gat->next = gptr; } gat = gptr; } } } } /* okay, do it */ done = FALSE; while((done == FALSE) && (retval == RETVAL_OK)) { len = sgetline(master->sockfd, &resp, master->do_ssl, master->ssl_struct); /* have we got the last one? */ if((len == 2) && (strcmp(resp, ".\n") == 0)) { done = TRUE; } else if(len < 0) { retval =RETVAL_ERROR; } else { retval = do_one_line(master, grpname, resp, glist, xoverkill->tie_delete); } } break; } } while(glist != NULL) { /* free up memory */ gptr = glist->next; free(glist); glist = gptr; } return retval; } /*-----------------------------------------------------------------------------------*/ int do_one_line(PMaster master, char *group, char *linein, PGrpList grpskill, int tie_delete) { /* take in one line of xover, then run it thru the kill routines */ int why = REASON_NONE, len, msgidlen = 0, retval = RETVAL_OK, match = REASON_NONE, keep = FALSE, del = FALSE; char *msgid = NULL, *ptr, *reason = NULL, tmp = '\0'; long msgnr; POverview overv; PGroup grpkill; PKillStruct masterk = master->xoverp; /* our master killstruct */ overv = master->xoverview; ptr = get_long(linein, &msgnr); /* get our message number */ /* first go thru and match up the header with the fields in the line, and fill */ /* in the pointers in the xoverview with pointers to start of field */ while( *ptr != '\0' && overv != NULL) { /* we are just past a tab */ len = 0; if(overv->full == TRUE) { /* have to skip header name in the xoverview */ while(*ptr != COLON && *ptr != '\0' && *ptr != '\t') { ptr++; } if(*ptr != '\0') { ptr++; /* move past colon */ } } overv->field = ptr; while(*ptr != '\t' && *ptr != '\0') { len++; ptr++; } overv->fieldlen = len; /* save Message-ID location for later passing to subroutines */ if(strcasecmp("Message-ID:", overv->header) == 0) { msgid = overv->field; msgidlen = overv->fieldlen; } overv = overv->next; if(*ptr == '\t') { ptr++; /* advance past tab */ } } while(overv != NULL) { /* in case we got a short xoverview */ overv->field = NULL; overv->fieldlen = 0; overv = overv->next; } if(msgid == NULL) { error_log(ERRLOG_REPORT, xover_phrases[13], linein, NULL); } else { if(masterk->child.Pid != -1) { /* send to child program */ match = (killprg_sendxover(master, linein) == TRUE) ? REASON_PRG : REASON_NONE; } #ifdef PERL_EMBED else if(masterk->perl_int != NULL) { /* send to perl subroutine */ match = (killperl_sendxover(master, linein) == TRUE) ? REASON_PERL : REASON_NONE; } #endif else { /* we have to check against master killfile and group kill file */ match = chk_a_group( master, &(masterk->master), master->xoverview, &reason); if((grpskill != NULL) && (match == REASON_NONE || masterk->grp_override == TRUE)) { /* we have to check against group */ while ( grpskill != NULL) { grpkill = grpskill->grp; match = chk_a_group( master, &(grpkill->match), master->xoverview, &reason); if(grpkill->delkeep == DELKEEP_KEEP) { if(match != REASON_NONE) { keep = TRUE; } else { del = TRUE; why = REASON_NOKEEP; } } else { if(match != REASON_NONE) { del = TRUE; why = match; } else { keep = TRUE; } } grpskill = grpskill->next; } /* now do tie-breaking */ if(del == FALSE && keep == FALSE) { /* match nothing */ match = REASON_NONE; } else if(del != keep) { /* either keep or del */ match = ( del == TRUE) ? why : REASON_NONE; } else { /* do tie-breaking */ match = (tie_delete == TRUE) ? REASON_TIE : REASON_NONE; } } } /* now we need to null terminate the msgid, for allocing or printing */ if(msgid != NULL) { tmp = msgid[msgidlen]; msgid[msgidlen] = '\0'; } if(match == REASON_NONE) { /* we keep it */ retval= allocnode(master, msgid, MANDATORY_OPTIONAL, group, msgnr); } else if(masterk->logyn != KILL_LOG_NONE) { /* only open this once */ if(masterk->logfp == NULL) { if((masterk->logfp = fopen(full_path(FP_GET, FP_TMPDIR, master->kill_log_name), "a")) == NULL) { MyPerror(xover_phrases[11]); } } if(masterk->logfp != NULL) { /* Log it */ if(match == REASON_HEADER) { print_phrases(masterk->logfp, xover_phrases[9], group, reason, msgid, NULL); } else { print_phrases(masterk->logfp, xover_phrases[9], group, xover_reasons[match], msgid, NULL); } /* restore the message-id end of string so entire line prints */ if(msgid != NULL) { msgid[msgidlen] = tmp; } if(masterk->xover_log_long == TRUE) { /* print the xover formatted to look like a message header */ overv = master->xoverview; while(overv != NULL ) { tmp = overv->field[overv->fieldlen]; /* null terminate it */ overv->field[overv->fieldlen] = '\0'; print_phrases(masterk->logfp, "%v1% %v2%\n", overv->header, overv->field); overv->field[overv->fieldlen] = tmp; /* restore it */ overv = overv->next ; } } else if(masterk->logyn == KILL_LOG_LONG) { /* print the xover as well */ print_phrases(masterk->logfp, xover_phrases[10],linein, NULL); } } } } return retval; } /*----------------------------------------------------------------------------------------*/ void get_xoverview(PMaster master) { /* get in the xoverview.fmt list, so we can parse what xover returns later */ /* we'll put em in a linked list */ int done, retval, len, full; char *resp; POverview tmp, tmp2, curptr; retval = RETVAL_OK; curptr = NULL; /* where we are currently at in the linked list */ if(send_command(master, "list overview.fmt\r\n", &resp, 215) == RETVAL_OK) { done = FALSE; /* now get em in, until we hit .\n which signifies end of response */ while(done != TRUE) { sgetline(master->sockfd, &resp, master->do_ssl, master->ssl_struct); if(master->debug == TRUE) { do_debug("Got line--%s", resp); } if(strcmp(resp, ".\n") == 0) { done = TRUE; } else if(retval == RETVAL_OK) { /* now save em if we haven't run out of memory */ len = strlen(resp); /* does this have a full flag on it, which means the field name */ /* will be in the xover string */ full = (strstr(resp, ":full") == NULL) ? FALSE : TRUE; /* now get rid of everything back to : */ while(resp[len] != ':') { resp[len--] = '\0'; } len++; /* so we point past colon */ if((tmp = malloc(sizeof(Overview))) == NULL) { error_log(ERRLOG_REPORT, xover_phrases[12], NULL); retval = RETVAL_ERROR; } else if((tmp->header = calloc(sizeof(char), len+1)) == NULL) { error_log(ERRLOG_REPORT, xover_phrases[12], NULL); retval = RETVAL_ERROR; } else { /* initialize the structure */ /* do this first so we don't wipe out with null termination */ strncpy(tmp->header, resp, len); /* so we get the colon */ tmp->header[len] = '\0'; tmp->next = NULL; tmp->field = NULL; tmp->fieldlen = 0; tmp->full = full; if(curptr == NULL) { /* at head of list */ curptr = tmp; master->xoverview = tmp; } else { /* add to linked list */ curptr->next = tmp; curptr = tmp; } } } } } if(retval != RETVAL_OK) { /* free up whatever alloced */ tmp = master->xoverview; while (tmp != NULL) { tmp2 = tmp; if(tmp->header != NULL) { free(tmp->header); } free(tmp); tmp = tmp2; } master->xoverview = NULL; } if(master->debug == TRUE && (tmp = master->xoverview) != NULL) { do_debug("--Xoverview.fmt list\n"); while(tmp != NULL) { if(tmp->header != NULL) { do_debug("item = %s -- full = %s\n", tmp->header, true_str(tmp->full)); } tmp = tmp->next; } do_debug("--End Xoverview.fmt list\n"); } } /*-------------------------------------------------------------------------------------*/ int chk_a_group(PMaster master, POneKill grp, POverview overv, char **reason) { /* return REASON_ if xover matches group */ /* linein should be at tab after the Message Number */ int i, match = REASON_NONE; unsigned long bytes; int lines; char tchar, *tptr; static char reasonstr[MAXLINLEN]; pmy_regex ptr; POverview tmp; *reason = reasonstr; tmp = overv; /* go thru each header field, and see if we must test against it */ while ( tmp != NULL && match == REASON_NONE) { /* only do the test if we have a valid header & field to test against */ if(tmp->field != NULL && tmp->header != NULL) { /* test the size of the body of the article */ if(grp->bodybig > 0 || grp->bodysmall > 0) { if(strcasecmp(tmp->header, "Bytes:") == 0) { sscanf(tmp->field, "%lu", &bytes); /* convert ascii to long */ if((grp->bodybig > 0) && (bytes > grp->bodybig)) { match = REASON_HIBYTES; } else if((grp->bodysmall > 0) && (bytes < grp->bodysmall)) { match = REASON_LOWBYTES; } } } /* test the number of lines in the article */ if((match == REASON_NONE) && (grp->hilines > 0 || grp->lowlines > 0)) { if(strcasecmp(tmp->header, "Lines:") == 0) { sscanf(tmp->field, "%d", &lines); if((grp->hilines > 0) && (lines > grp->hilines)) { match = REASON_HILINES; } else if((grp->lowlines > 0) && (lines < grp->lowlines)) { match = REASON_LOWLINES; } } } /* check the number of xrefs */ if(match == REASON_NONE && grp->maxxref > 0) { if(strcasecmp(tmp->header, "Xref:") == 0) { i = 0; tptr = tmp->field; while(i <= grp->maxxref && *tptr != '\0' ) { if(*tptr == COLON ) { i++; } tptr++; } if(i > grp->maxxref) { match = REASON_XREF; } } } /* match against any of the headers */ if(match == REASON_NONE) { ptr = grp->list; while(match == REASON_NONE && ptr != NULL) { if(strcasecmp(tmp->header, ptr->header) == 0) { /* we need to null terminate the field and restore it later */ tchar = tmp->field[tmp->fieldlen]; tmp->field[tmp->fieldlen] = '\0'; if(regex_block(tmp->field, ptr, master->debug) == TRUE) { match = REASON_HEADER; sprintf(reasonstr, "%s-%s%s", xover_reasons[REASON_HEADER], tmp->header, tmp->field); } tmp->field[tmp->fieldlen] = tchar; } ptr = ptr->next; } } } tmp = tmp->next; } return match; } /*---------------------------------------------------------------------------*/ int match_group(char *match_grp, char *group, int debug) { /* does match match group? match may contain wildcards */ int match = FALSE; if(match_grp != NULL && group != NULL) { if(debug == TRUE) { do_debug("Xover - matching %s against %s\n", match_grp, group); } while( *group == *match_grp && *group != '\0') { group++; match_grp++; } if(*match_grp == '\0' || *match_grp == '*') { /* wildcard match or end of string, they match so far, so they match */ match = TRUE; } if(debug == TRUE) { do_debug("match = %s\n", true_str(match)); } } return match; } /*-------------------------------------------------------------------------------*/ int get_xover(PMaster master, char *group, long startnr, long endnr) { int len, nr, done, retval = RETVAL_OK; char cmd[MAXLINLEN], *resp, *ptr, *msgid; long msgnr; sprintf(cmd, "xover %ld-%ld\r\n", startnr,endnr); retval = send_command(master, cmd, &resp, 0); if(retval == RETVAL_OK) { number(resp, &nr); switch(nr) { default: case 412: case 502: /* abort on these errors */ error_log(ERRLOG_REPORT, xover_phrases[0], resp, NULL); retval = RETVAL_NOXOVER; break; case 420: /* no articles available, no prob */ break; case 224: /* got a list coming at us */ done = FALSE; while((done == FALSE) && (retval == RETVAL_OK)) { len = sgetline(master->sockfd, &resp, master->do_ssl, master->ssl_struct); if(master->debug == TRUE) { do_debug("Got xover line: %s", resp); } /* have we got the last one? */ if((len == 2) && (strcmp(resp, ".\n") == 0)) { done = TRUE; } else if(len < 0) { retval =RETVAL_ERROR; } else { ptr = get_long(resp, &msgnr); /* get our message number */ msgid = find_msgid(master, ptr); /* find the msg-id */ if(msgid == NULL) { error_log(ERRLOG_REPORT, xover_phrases[13], resp); } else { /* alloc the memory for it */ retval= allocnode(master, msgid, MANDATORY_OPTIONAL, group, msgnr); } } } break; } } return retval; } /*-----------------------------------------------------------------------------------*/ /* find the msgid in a xover, and return it null-terminated, so can alloc memory, etc*/ /*-----------------------------------------------------------------------------------*/ char *find_msgid(PMaster master, char *xover) { char *ptr, *ptr2, *retval = NULL; POverview pov; pov = master->xoverview; ptr = xover; /* now go thru and find the Message-ID in the xoverview, using the overview.fmt */ /* to tell us which field is which */ if(ptr != NULL) { while ( *ptr != '\0' && pov != NULL && strcmp(pov->header, "Message-ID:") != 0) { /* go to the next field, past the tab */ pov = pov->next; while(*ptr != '\t' && *ptr != '\0') { ptr++; } if(*ptr != '\0') { ptr++; /* past the tab */ } } if(*ptr != '\0' && pov != NULL) { /* bingo, found it */ /* find the start of the msgid */ while(*ptr != '\0' && *ptr != '<') { ptr++; } if(ptr != NULL) { ptr2 = ptr; /* NULL terminate the msgid */ while(*ptr2 != '\0' && *ptr2 != '>') { ptr2++; } /* ONLY if we find a valid start and end to the MessageId */ /* do we set a valid return value */ if(*ptr2 != '\0') { *(ptr2+1) = '\0'; retval = ptr; } } } } return retval; } suck-4.3.4/xover.h000066400000000000000000000003221333033562000137760ustar00rootroot00000000000000#ifndef SUCK_XOVER_H /*-----------------------prototypes */ int do_group_xover(PMaster, char *, long, long); int get_xover(PMaster, char *, long, long); void get_xoverview(PMaster); #endif /* SUCK_XOVER_H */