#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; struct hostent *hi; 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, &hi, 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); } }