900 lines
19 KiB
C
900 lines
19 KiB
C
|
/**
|
||
|
* ircd-ratbox: A slightly useful ircd.
|
||
|
* bantool.c: The ircd-ratbox database managment tool.
|
||
|
*
|
||
|
* Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center
|
||
|
* Copyright (C) 1996-2002 Hybrid Development Team
|
||
|
* Copyright (C) 2002-2008 ircd-ratbox development team
|
||
|
* Copyright (C) 2008 Daniel J Reidy <dubkat@gmail.com>
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation; either version 2 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program; if not, write to the Free Software
|
||
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||
|
* USA
|
||
|
*
|
||
|
* $Id: bantool.c 26164 2008-10-26 19:52:43Z androsyn $
|
||
|
*
|
||
|
*
|
||
|
* The following server admins have either contributed various configs to test against,
|
||
|
* or helped with debugging and feature requests. Many thanks to them.
|
||
|
* stevoo / efnet.port80.se
|
||
|
* AndroSyn / irc2.choopa.net, irc.igs.ca
|
||
|
* Salvation / irc.blessed.net
|
||
|
* JamesOff / efnet.demon.co.uk
|
||
|
*
|
||
|
* Thanks to AndroSyn for challenging me to learn C on the fly :)
|
||
|
* BUGS Direct Question, Bug Reports, and Feature Requests to #ratbox on EFnet.
|
||
|
* BUGS Complaints >/dev/null
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#define _XOPEN_SOURCE
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <time.h>
|
||
|
|
||
|
#include "stdinc.h"
|
||
|
#include "rsdb.h"
|
||
|
|
||
|
#define EmptyString(x) ((x == NULL) || (*(x) == '\0'))
|
||
|
#define CheckEmpty(x) EmptyString(x) ? "" : x
|
||
|
|
||
|
#define BT_VERSION "0.4.1"
|
||
|
|
||
|
typedef enum
|
||
|
{
|
||
|
BANDB_KLINE,
|
||
|
BANDB_KLINE_PERM,
|
||
|
BANDB_DLINE,
|
||
|
BANDB_DLINE_PERM,
|
||
|
BANDB_XLINE,
|
||
|
BANDB_XLINE_PERM,
|
||
|
BANDB_RESV,
|
||
|
BANDB_RESV_PERM,
|
||
|
LAST_BANDB_TYPE
|
||
|
} bandb_type;
|
||
|
|
||
|
|
||
|
static char bandb_letter[LAST_BANDB_TYPE] = {
|
||
|
'K', 'K', 'D', 'D', 'X', 'X', 'R', 'R'
|
||
|
};
|
||
|
|
||
|
static const char *bandb_table[LAST_BANDB_TYPE] = {
|
||
|
"kline", "kline", "dline", "dline", "xline", "xline", "resv", "resv"
|
||
|
};
|
||
|
|
||
|
static const char *bandb_suffix[LAST_BANDB_TYPE] = {
|
||
|
"", ".perm",
|
||
|
"", ".perm",
|
||
|
"", ".perm",
|
||
|
"", ".perm"
|
||
|
};
|
||
|
|
||
|
static char me[PATH_MAX];
|
||
|
|
||
|
/* *INDENT-OFF* */
|
||
|
/* report counters */
|
||
|
struct counter
|
||
|
{
|
||
|
unsigned int klines;
|
||
|
unsigned int dlines;
|
||
|
unsigned int xlines;
|
||
|
unsigned int resvs;
|
||
|
unsigned int error;
|
||
|
} count = {0, 0, 0, 0, 0};
|
||
|
|
||
|
/* flags set by command line options */
|
||
|
struct flags
|
||
|
{
|
||
|
int none;
|
||
|
int export;
|
||
|
int import;
|
||
|
int verify;
|
||
|
int vacuum;
|
||
|
int pretend;
|
||
|
int verbose;
|
||
|
int wipe;
|
||
|
int dupes_ok;
|
||
|
} flag = {YES, NO, NO, NO, NO, NO, NO, NO, NO};
|
||
|
/* *INDENT-ON* */
|
||
|
|
||
|
static int table_has_rows(const char *table);
|
||
|
static int table_exists(const char *table);
|
||
|
|
||
|
static const char *clean_gecos_field(const char *gecos);
|
||
|
static char *bt_smalldate(const char *string);
|
||
|
static char *getfield(char *newline);
|
||
|
static char *strip_quotes(const char *string);
|
||
|
static char *mangle_reason(const char *string);
|
||
|
static char *escape_quotes(const char *string);
|
||
|
|
||
|
static void db_error_cb(const char *errstr);
|
||
|
static void db_reclaim_slack(void);
|
||
|
static void export_config(const char *conf, int id);
|
||
|
static void import_config(const char *conf, int id);
|
||
|
static void check_schema(void);
|
||
|
static void print_help(int i_exit);
|
||
|
static void wipe_schema(void);
|
||
|
static void drop_dupes(const char *user, const char *host, const char *t);
|
||
|
|
||
|
/**
|
||
|
* swing your pants
|
||
|
*/
|
||
|
int
|
||
|
main(int argc, char *argv[])
|
||
|
{
|
||
|
char etc[PATH_MAX];
|
||
|
char conf[PATH_MAX];
|
||
|
int opt;
|
||
|
int i;
|
||
|
|
||
|
rb_strlcpy(me, argv[0], sizeof(me));
|
||
|
|
||
|
while((opt = getopt(argc, argv, "hieuspvwd")) != -1)
|
||
|
{
|
||
|
switch (opt)
|
||
|
{
|
||
|
case 'h':
|
||
|
print_help(EXIT_SUCCESS);
|
||
|
break;
|
||
|
case 'i':
|
||
|
flag.none = NO;
|
||
|
flag.import = YES;
|
||
|
break;
|
||
|
case 'e':
|
||
|
flag.none = NO;
|
||
|
flag.export = YES;
|
||
|
break;
|
||
|
case 'u':
|
||
|
flag.none = NO;
|
||
|
flag.verify = YES;
|
||
|
break;
|
||
|
case 's':
|
||
|
flag.none = NO;
|
||
|
flag.vacuum = YES;
|
||
|
break;
|
||
|
case 'p':
|
||
|
flag.pretend = YES;
|
||
|
break;
|
||
|
case 'v':
|
||
|
flag.verbose = YES;
|
||
|
break;
|
||
|
case 'w':
|
||
|
flag.wipe = YES;
|
||
|
break;
|
||
|
case 'd':
|
||
|
flag.dupes_ok = YES;
|
||
|
break;
|
||
|
default: /* '?' */
|
||
|
print_help(EXIT_FAILURE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* they should really read the help. */
|
||
|
if(flag.none)
|
||
|
print_help(EXIT_FAILURE);
|
||
|
|
||
|
if((flag.import && flag.export) || (flag.export && flag.wipe)
|
||
|
|| (flag.verify && flag.pretend) || (flag.export && flag.pretend))
|
||
|
{
|
||
|
fprintf(stderr, "* Error: Conflicting flags.\n");
|
||
|
if(flag.export && flag.pretend)
|
||
|
fprintf(stderr, "* There is nothing to 'pretend' when exporting.\n");
|
||
|
|
||
|
fprintf(stderr, "* For an explination of commands, run: %s -h\n", me);
|
||
|
exit(EXIT_FAILURE);
|
||
|
}
|
||
|
|
||
|
if(argv[optind] != NULL)
|
||
|
rb_strlcpy(etc, argv[optind], sizeof(etc));
|
||
|
else
|
||
|
rb_strlcpy(etc, ETCPATH, sizeof(ETCPATH));
|
||
|
|
||
|
fprintf(stdout,
|
||
|
"* ircd-ratbox bantool v.%s ($Id: bantool.c 26164 2008-10-26 19:52:43Z androsyn $)\n",
|
||
|
BT_VERSION);
|
||
|
|
||
|
if(flag.pretend == NO)
|
||
|
{
|
||
|
if(rsdb_init(db_error_cb) == -1)
|
||
|
{
|
||
|
fprintf(stderr, "* Error: Unable to open database\n");
|
||
|
exit(EXIT_FAILURE);
|
||
|
}
|
||
|
check_schema();
|
||
|
|
||
|
if(flag.vacuum)
|
||
|
db_reclaim_slack();
|
||
|
|
||
|
if(flag.import && flag.wipe)
|
||
|
{
|
||
|
flag.dupes_ok = YES; /* dont check for dupes if we are wiping the db clean */
|
||
|
for(i = 0; i < 3; i++)
|
||
|
fprintf(stdout,
|
||
|
"* WARNING: YOU ARE ABOUT TO WIPE YOUR DATABASE!\n");
|
||
|
|
||
|
fprintf(stdout, "* Press ^C to abort! ");
|
||
|
fflush(stdout);
|
||
|
rb_sleep(10, 0);
|
||
|
fprintf(stdout, "Carrying on...\n");
|
||
|
wipe_schema();
|
||
|
}
|
||
|
}
|
||
|
if(flag.verbose && flag.dupes_ok == YES)
|
||
|
fprintf(stdout, "* Allowing duplicate bans...\n");
|
||
|
|
||
|
/* checking for our files to import or export */
|
||
|
for(i = 0; i < LAST_BANDB_TYPE; i++)
|
||
|
{
|
||
|
rb_snprintf(conf, sizeof(conf), "%s/%s.conf%s",
|
||
|
etc, bandb_table[i], bandb_suffix[i]);
|
||
|
|
||
|
if(flag.import && flag.pretend == NO)
|
||
|
rsdb_transaction(RSDB_TRANS_START);
|
||
|
|
||
|
if(flag.import)
|
||
|
import_config(conf, i);
|
||
|
|
||
|
if(flag.export)
|
||
|
export_config(conf, i);
|
||
|
|
||
|
if(flag.import && flag.pretend == NO)
|
||
|
rsdb_transaction(RSDB_TRANS_END);
|
||
|
}
|
||
|
|
||
|
if(flag.import)
|
||
|
{
|
||
|
if(count.error && flag.verbose)
|
||
|
fprintf(stderr, "* I was unable to locate %i config files to import.\n",
|
||
|
count.error);
|
||
|
|
||
|
fprintf(stdout, "* Import Stats: Klines: %i, Dlines: %i, Xlines: %i, Resvs: %i \n",
|
||
|
count.klines, count.dlines, count.xlines, count.resvs);
|
||
|
|
||
|
fprintf(stdout,
|
||
|
"*\n* If your IRC server is currently running, newly imported bans \n* will not take effect until you issue the command: /quote rehash bans\n");
|
||
|
|
||
|
if(flag.pretend)
|
||
|
fprintf(stdout,
|
||
|
"* Pretend mode engaged. Nothing was actually entered into the database.\n");
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* export the database to old-style flat files
|
||
|
*/
|
||
|
static void
|
||
|
export_config(const char *conf, int id)
|
||
|
{
|
||
|
struct rsdb_table table;
|
||
|
static char sql[BUFSIZE * 2];
|
||
|
static char buf[512];
|
||
|
FILE *fd = NULL;
|
||
|
int j;
|
||
|
|
||
|
/* for sanity sake */
|
||
|
const int mask1 = 0;
|
||
|
const int mask2 = 1;
|
||
|
const int reason = 2;
|
||
|
const int oper = 3;
|
||
|
const int ts = 4;
|
||
|
/* const int perm = 5; */
|
||
|
|
||
|
if(!table_has_rows(bandb_table[id]))
|
||
|
return;
|
||
|
|
||
|
if(strstr(conf, ".perm") != 0)
|
||
|
rb_snprintf(sql, sizeof(sql),
|
||
|
"SELECT DISTINCT mask1,mask2,reason,oper,time FROM %s WHERE perm = 1 ORDER BY time",
|
||
|
bandb_table[id]);
|
||
|
else
|
||
|
rb_snprintf(sql, sizeof(sql),
|
||
|
"SELECT DISTINCT mask1,mask2,reason,oper,time FROM %s WHERE perm = 0 ORDER BY time",
|
||
|
bandb_table[id]);
|
||
|
|
||
|
rsdb_exec_fetch(&table, sql);
|
||
|
if(table.row_count <= 0)
|
||
|
{
|
||
|
rsdb_exec_fetch_end(&table);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(flag.verbose)
|
||
|
fprintf(stdout, "* checking for %s: ", conf); /* debug */
|
||
|
|
||
|
/* open config for reading, or skip to the next */
|
||
|
if(!(fd = fopen(conf, "w")))
|
||
|
{
|
||
|
if(flag.verbose)
|
||
|
fprintf(stdout, "\tmissing.\n");
|
||
|
count.error++;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for(j = 0; j < table.row_count; j++)
|
||
|
{
|
||
|
switch (id)
|
||
|
{
|
||
|
case BANDB_DLINE:
|
||
|
case BANDB_DLINE_PERM:
|
||
|
rb_snprintf(buf, sizeof(buf),
|
||
|
"\"%s\",\"%s\",\"\",\"%s\",\"%s\",%s\n",
|
||
|
table.row[j][mask1],
|
||
|
mangle_reason(table.row[j][reason]),
|
||
|
bt_smalldate(table.row[j][ts]),
|
||
|
table.row[j][oper], table.row[j][ts]);
|
||
|
break;
|
||
|
|
||
|
case BANDB_XLINE:
|
||
|
case BANDB_XLINE_PERM:
|
||
|
rb_snprintf(buf, sizeof(buf),
|
||
|
"\"%s\",\"0\",\"%s\",\"%s\",%s\n",
|
||
|
escape_quotes(table.row[j][mask1]),
|
||
|
mangle_reason(table.row[j][reason]),
|
||
|
table.row[j][oper], table.row[j][ts]);
|
||
|
break;
|
||
|
|
||
|
case BANDB_RESV:
|
||
|
case BANDB_RESV_PERM:
|
||
|
rb_snprintf(buf, sizeof(buf),
|
||
|
"\"%s\",\"%s\",\"%s\",%s\n",
|
||
|
table.row[j][mask1],
|
||
|
mangle_reason(table.row[j][reason]),
|
||
|
table.row[j][oper], table.row[j][ts]);
|
||
|
break;
|
||
|
|
||
|
|
||
|
default: /* Klines */
|
||
|
rb_snprintf(buf, sizeof(buf),
|
||
|
"\"%s\",\"%s\",\"%s\",\"\",\"%s\",\"%s\",%s\n",
|
||
|
table.row[j][mask1], table.row[j][mask2],
|
||
|
mangle_reason(table.row[j][reason]),
|
||
|
bt_smalldate(table.row[j][ts]), table.row[j][oper],
|
||
|
table.row[j][ts]);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
fprintf(fd, "%s", buf);
|
||
|
}
|
||
|
|
||
|
rsdb_exec_fetch_end(&table);
|
||
|
if(flag.verbose)
|
||
|
fprintf(stdout, "\twritten.\n");
|
||
|
fclose(fd);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* attempt to condense the individual conf functions into one
|
||
|
*/
|
||
|
static void
|
||
|
import_config(const char *conf, int id)
|
||
|
{
|
||
|
FILE *fd;
|
||
|
|
||
|
char line[BUFSIZE];
|
||
|
char *p;
|
||
|
int i = 0;
|
||
|
|
||
|
char f_perm = 0;
|
||
|
char *f_mask1 = NULL;
|
||
|
char *f_mask2 = NULL;
|
||
|
char *f_oper = NULL;
|
||
|
char *f_time = NULL;
|
||
|
char *f_reason = NULL;
|
||
|
char *f_oreason = NULL;
|
||
|
char newreason[REASONLEN];
|
||
|
|
||
|
if(flag.verbose)
|
||
|
fprintf(stdout, "* checking for %s: ", conf); /* debug */
|
||
|
|
||
|
/* open config for reading, or skip to the next */
|
||
|
if(!(fd = fopen(conf, "r")))
|
||
|
{
|
||
|
if(flag.verbose)
|
||
|
fprintf(stdout, "%*s", strlen(bandb_suffix[id]) > 0 ? 10 : 15,
|
||
|
"missing.\n");
|
||
|
count.error++;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(strstr(conf, ".perm") != 0)
|
||
|
f_perm = 1;
|
||
|
|
||
|
|
||
|
/* xline
|
||
|
* "SYSTEM","0","banned","stevoo!stevoo@efnet.port80.se{stevoo}",1111080437
|
||
|
* resv
|
||
|
* "OseK","banned nickname","stevoo!stevoo@efnet.port80.se{stevoo}",1111031619
|
||
|
* dline
|
||
|
* "194.158.192.0/19","laptop scammers","","2005/3/17 05.33","stevoo!stevoo@efnet.port80.se{stevoo}",1111033988
|
||
|
*/
|
||
|
while(fgets(line, sizeof(line), fd))
|
||
|
{
|
||
|
if((p = strpbrk(line, "\r\n")) != NULL)
|
||
|
*p = '\0';
|
||
|
|
||
|
if((*line == '\0') || (*line == '#'))
|
||
|
continue;
|
||
|
|
||
|
/* mask1 */
|
||
|
f_mask1 = getfield(line);
|
||
|
|
||
|
if(EmptyString(f_mask1))
|
||
|
continue;
|
||
|
|
||
|
/* mask2 */
|
||
|
switch (id)
|
||
|
{
|
||
|
case BANDB_XLINE:
|
||
|
case BANDB_XLINE_PERM:
|
||
|
f_mask1 = escape_quotes(clean_gecos_field(f_mask1));
|
||
|
getfield(NULL); /* empty field */
|
||
|
break;
|
||
|
|
||
|
case BANDB_RESV:
|
||
|
case BANDB_RESV_PERM:
|
||
|
case BANDB_DLINE:
|
||
|
case BANDB_DLINE_PERM:
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
f_mask2 = getfield(NULL);
|
||
|
if(EmptyString(f_mask2))
|
||
|
continue;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* reason */
|
||
|
f_reason = getfield(NULL);
|
||
|
if(EmptyString(f_reason))
|
||
|
continue;
|
||
|
|
||
|
/* oper comment */
|
||
|
switch (id)
|
||
|
{
|
||
|
case BANDB_KLINE:
|
||
|
case BANDB_KLINE_PERM:
|
||
|
case BANDB_DLINE:
|
||
|
case BANDB_DLINE_PERM:
|
||
|
f_oreason = getfield(NULL);
|
||
|
getfield(NULL);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
f_oper = getfield(NULL);
|
||
|
f_time = strip_quotes(f_oper + strlen(f_oper) + 2);
|
||
|
|
||
|
/* meh */
|
||
|
if(id == BANDB_KLINE || id == BANDB_KLINE_PERM)
|
||
|
{
|
||
|
if(strstr(f_mask1, "!") != NULL)
|
||
|
{
|
||
|
fprintf(stderr,
|
||
|
"* SKIPPING INVALID KLINE %s@%s set by %s\n",
|
||
|
f_mask1, f_mask2, f_oper);
|
||
|
fprintf(stderr, " You may wish to re-apply it correctly.\n");
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* append operreason_field to reason_field */
|
||
|
if(!EmptyString(f_oreason))
|
||
|
rb_snprintf(newreason, sizeof(newreason), "%s | %s", f_reason, f_oreason);
|
||
|
else
|
||
|
rb_snprintf(newreason, sizeof(newreason), "%s", f_reason);
|
||
|
|
||
|
if(flag.pretend == NO)
|
||
|
{
|
||
|
if(flag.dupes_ok == NO)
|
||
|
drop_dupes(f_mask1, f_mask2, bandb_table[id]);
|
||
|
|
||
|
rsdb_exec(NULL,
|
||
|
"INSERT INTO %s (mask1, mask2, oper, time, perm, reason) VALUES('%Q','%Q','%Q','%Q','%d','%Q')",
|
||
|
bandb_table[id], f_mask1, f_mask2, f_oper, f_time, f_perm,
|
||
|
newreason);
|
||
|
}
|
||
|
|
||
|
if(flag.pretend && flag.verbose)
|
||
|
fprintf(stdout,
|
||
|
"%s: perm(%d) mask1(%s) mask2(%s) oper(%s) reason(%s) time(%s)\n",
|
||
|
bandb_table[id], f_perm, f_mask1, f_mask2, f_oper, newreason,
|
||
|
f_time);
|
||
|
|
||
|
i++;
|
||
|
}
|
||
|
|
||
|
switch (bandb_letter[id])
|
||
|
{
|
||
|
case 'K':
|
||
|
count.klines += i;
|
||
|
break;
|
||
|
case 'D':
|
||
|
count.dlines += i;
|
||
|
break;
|
||
|
case 'X':
|
||
|
count.xlines += i;
|
||
|
break;
|
||
|
case 'R':
|
||
|
count.resvs += i;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if(flag.verbose)
|
||
|
fprintf(stdout, "%*s\n", strlen(bandb_suffix[id]) > 0 ? 10 : 15, "imported.");
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* getfield
|
||
|
*
|
||
|
* inputs - input buffer
|
||
|
* output - next field
|
||
|
* side effects - field breakup for ircd.conf file.
|
||
|
*/
|
||
|
char *
|
||
|
getfield(char *newline)
|
||
|
{
|
||
|
static char *line = NULL;
|
||
|
char *end, *field;
|
||
|
|
||
|
if(newline != NULL)
|
||
|
line = newline;
|
||
|
|
||
|
if(line == NULL)
|
||
|
return (NULL);
|
||
|
|
||
|
field = line;
|
||
|
|
||
|
/* XXX make this skip to first " if present */
|
||
|
if(*field == '"')
|
||
|
field++;
|
||
|
else
|
||
|
return (NULL); /* mal-formed field */
|
||
|
|
||
|
end = strchr(line, ',');
|
||
|
|
||
|
while(1)
|
||
|
{
|
||
|
/* no trailing , - last field */
|
||
|
if(end == NULL)
|
||
|
{
|
||
|
end = line + strlen(line);
|
||
|
line = NULL;
|
||
|
|
||
|
if(*end == '"')
|
||
|
{
|
||
|
*end = '\0';
|
||
|
return field;
|
||
|
}
|
||
|
else
|
||
|
return NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* look for a ", to mark the end of a field.. */
|
||
|
if(*(end - 1) == '"')
|
||
|
{
|
||
|
line = end + 1;
|
||
|
end--;
|
||
|
*end = '\0';
|
||
|
return field;
|
||
|
}
|
||
|
|
||
|
/* search for the next ',' */
|
||
|
end++;
|
||
|
end = strchr(end, ',');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* strip away "quotes" from around strings
|
||
|
*/
|
||
|
static char *
|
||
|
strip_quotes(const char *string)
|
||
|
{
|
||
|
static char buf[14]; /* int(11) + 2 + \0 */
|
||
|
char *str = buf;
|
||
|
|
||
|
if(string == NULL)
|
||
|
return NULL;
|
||
|
|
||
|
while(*string)
|
||
|
{
|
||
|
if(*string != '"')
|
||
|
{
|
||
|
*str++ = *string;
|
||
|
}
|
||
|
string++;
|
||
|
}
|
||
|
*str = '\0';
|
||
|
return buf;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* escape quotes in a string
|
||
|
*/
|
||
|
static char *
|
||
|
escape_quotes(const char *string)
|
||
|
{
|
||
|
static char buf[BUFSIZE * 2];
|
||
|
char *str = buf;
|
||
|
|
||
|
if(string == NULL)
|
||
|
return NULL;
|
||
|
|
||
|
while(*string)
|
||
|
{
|
||
|
if(*string == '"')
|
||
|
{
|
||
|
*str++ = '\\';
|
||
|
*str++ = '"';
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*str++ = *string;
|
||
|
}
|
||
|
string++;
|
||
|
}
|
||
|
*str = '\0';
|
||
|
return buf;
|
||
|
}
|
||
|
|
||
|
|
||
|
static char *
|
||
|
mangle_reason(const char *string)
|
||
|
{
|
||
|
static char buf[BUFSIZE * 2];
|
||
|
char *str = buf;
|
||
|
|
||
|
if(string == NULL)
|
||
|
return NULL;
|
||
|
|
||
|
while(*string)
|
||
|
{
|
||
|
switch (*string)
|
||
|
{
|
||
|
case '"':
|
||
|
*str = '\'';
|
||
|
break;
|
||
|
case ':':
|
||
|
*str = ' ';
|
||
|
break;
|
||
|
default:
|
||
|
*str = *string;
|
||
|
}
|
||
|
string++;
|
||
|
str++;
|
||
|
|
||
|
}
|
||
|
*str = '\0';
|
||
|
return buf;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* change spaces to \s in gecos field
|
||
|
*/
|
||
|
static const char *
|
||
|
clean_gecos_field(const char *gecos)
|
||
|
{
|
||
|
static char buf[BUFSIZE * 2];
|
||
|
char *str = buf;
|
||
|
|
||
|
if(gecos == NULL)
|
||
|
return NULL;
|
||
|
|
||
|
while(*gecos)
|
||
|
{
|
||
|
if(*gecos == ' ')
|
||
|
{
|
||
|
*str++ = '\\';
|
||
|
*str++ = 's';
|
||
|
}
|
||
|
else
|
||
|
*str++ = *gecos;
|
||
|
gecos++;
|
||
|
}
|
||
|
*str = '\0';
|
||
|
return buf;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* verify the database integrity, and if necessary create apropriate tables
|
||
|
*/
|
||
|
static void
|
||
|
check_schema(void)
|
||
|
{
|
||
|
int i, j;
|
||
|
char type[8]; /* longest string is 'INTEGER\0' */
|
||
|
|
||
|
if(flag.verify || flag.verbose)
|
||
|
fprintf(stdout, "* Verifying database.\n");
|
||
|
|
||
|
const char *columns[] = {
|
||
|
"perm",
|
||
|
"mask1",
|
||
|
"mask2",
|
||
|
"oper",
|
||
|
"time",
|
||
|
"reason",
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
for(i = 0; i < LAST_BANDB_TYPE; i++)
|
||
|
{
|
||
|
if(!table_exists(bandb_table[i]))
|
||
|
{
|
||
|
rsdb_exec(NULL,
|
||
|
"CREATE TABLE %s (mask1 TEXT, mask2 TEXT, oper TEXT, time INTEGER, perm INTEGER, reason TEXT)",
|
||
|
bandb_table[i]);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* i can't think of any better way to do this, other then attempt to
|
||
|
* force the creation of column that may, or may not already exist. --dubkat
|
||
|
*/
|
||
|
else
|
||
|
{
|
||
|
for(j = 0; columns[j] != NULL; j++)
|
||
|
{
|
||
|
if(!strcmp(columns[j], "time") && !strcmp(columns[j], "perm"))
|
||
|
rb_strlcpy(type, "INTEGER", sizeof(type));
|
||
|
else
|
||
|
rb_strlcpy(type, "TEXT", sizeof(type));
|
||
|
|
||
|
/* attempt to add a column with extreme prejudice, errors are ignored */
|
||
|
rsdb_exec(NULL, "ALTER TABLE %s ADD COLUMN %s %s", bandb_table[i],
|
||
|
columns[j], type);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
i++; /* skip over .perm */
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
db_reclaim_slack(void)
|
||
|
{
|
||
|
fprintf(stdout, "* Reclaiming free space.\n");
|
||
|
rsdb_exec(NULL, "VACUUM");
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* check that appropriate tables exist.
|
||
|
*/
|
||
|
static int
|
||
|
table_exists(const char *dbtab)
|
||
|
{
|
||
|
struct rsdb_table table;
|
||
|
rsdb_exec_fetch(&table, "SELECT name FROM sqlite_master WHERE type='table' AND name='%s'",
|
||
|
dbtab);
|
||
|
rsdb_exec_fetch_end(&table);
|
||
|
return table.row_count;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* check that there are actual entries in a table
|
||
|
*/
|
||
|
static int
|
||
|
table_has_rows(const char *dbtab)
|
||
|
{
|
||
|
struct rsdb_table table;
|
||
|
rsdb_exec_fetch(&table, "SELECT * FROM %s", dbtab);
|
||
|
rsdb_exec_fetch_end(&table);
|
||
|
return table.row_count;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* completly wipes out an existing ban.db of all entries.
|
||
|
*/
|
||
|
static void
|
||
|
wipe_schema(void)
|
||
|
{
|
||
|
int i;
|
||
|
rsdb_transaction(RSDB_TRANS_START);
|
||
|
for(i = 0; i < LAST_BANDB_TYPE; i++)
|
||
|
{
|
||
|
rsdb_exec(NULL, "DROP TABLE %s", bandb_table[i]);
|
||
|
i++; /* double increment to skip over .perm */
|
||
|
}
|
||
|
rsdb_transaction(RSDB_TRANS_END);
|
||
|
|
||
|
check_schema();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* remove pre-existing duplicate bans from the database.
|
||
|
* we favor the new, imported ban over the one in the database
|
||
|
*/
|
||
|
void
|
||
|
drop_dupes(const char *user, const char *host, const char *t)
|
||
|
{
|
||
|
rsdb_exec(NULL, "DELETE FROM %s WHERE mask1='%Q' AND mask2='%Q'", t, user, host);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
db_error_cb(const char *errstr)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* convert unix timestamp to human readable (small) date
|
||
|
*/
|
||
|
static char *
|
||
|
bt_smalldate(const char *string)
|
||
|
{
|
||
|
static char buf[MAX_DATE_STRING];
|
||
|
struct tm *lt;
|
||
|
time_t t;
|
||
|
t = strtol(string, NULL, 10);
|
||
|
lt = gmtime(&t);
|
||
|
if(lt == NULL)
|
||
|
return NULL;
|
||
|
rb_snprintf(buf, sizeof(buf), "%d/%d/%d %02d.%02d",
|
||
|
lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday, lt->tm_hour, lt->tm_min);
|
||
|
return buf;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* you are here ->.
|
||
|
*/
|
||
|
void
|
||
|
print_help(int i_exit)
|
||
|
{
|
||
|
fprintf(stderr, "bantool v.%s - the ircd-ratbox database tool.\n", BT_VERSION);
|
||
|
fprintf(stderr, "Copyright (C) 2008 Daniel J Reidy <dubkat@gmail.com>\n");
|
||
|
fprintf(stderr, "$Id: bantool.c 26164 2008-10-26 19:52:43Z androsyn $\n\n");
|
||
|
fprintf(stderr, "This program is distributed in the hope that it will be useful,\n"
|
||
|
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
|
||
|
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
|
||
|
"GNU General Public License for more details.\n\n");
|
||
|
|
||
|
fprintf(stderr, "Usage: %s <-i|-e> [-p] [-v] [-h] [-d] [-w] [path]\n", me);
|
||
|
fprintf(stderr, " -h : Display some slightly useful help.\n");
|
||
|
fprintf(stderr, " -i : Actually import configs into your database.\n");
|
||
|
fprintf(stderr, " -e : Export your database to old-style flat files.\n");
|
||
|
fprintf(stderr,
|
||
|
" This is suitable for redistrubuting your banlists, or creating backups.\n");
|
||
|
fprintf(stderr, " -s : Reclaim empty slack space the database may be taking up.\n");
|
||
|
fprintf(stderr, " -u : Update the database tables to support any new features.\n");
|
||
|
fprintf(stderr,
|
||
|
" This is automaticlly done if you are importing or exporting\n");
|
||
|
fprintf(stderr, " but should be run whenever you upgrade the ircd.\n");
|
||
|
fprintf(stderr,
|
||
|
" -p : pretend, checks for the configs, and parses them, then tells you some data...\n");
|
||
|
fprintf(stderr, " but does not touch your database.\n");
|
||
|
fprintf(stderr,
|
||
|
" -v : Be verbose... and it *is* very verbose! (intended for debugging)\n");
|
||
|
fprintf(stderr, " -d : Enable checking for redunant entries.\n");
|
||
|
fprintf(stderr, " -w : Completly wipe your database clean. May be used with -i \n");
|
||
|
fprintf(stderr,
|
||
|
" path : An optional directory containing old ratbox configs for import, or export.\n");
|
||
|
fprintf(stderr, " If not specified, it looks in PREFIX/etc.\n");
|
||
|
exit(i_exit);
|
||
|
}
|