#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 */ fscanf(f_lock, "%ld", (long *) &pid); fclose(f_lock); 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); } }