#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_DIRENT_H
# include <dirent.h>
#else
# define dirent direct
# ifdef HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# ifdef HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
#endif
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <signal.h>
#include <sys/syslimits.h>
#ifdef DMALLOC
#include <dmalloc.h>
#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;i<master->nrgroups;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);
}
}
syntax highlighted by Code2HTML, v. 0.9.1