/*
* -------------------------------------------------------
* Copyright (C) 2003-2007 Tommi Saviranta <wnd@iki.fi>
* -------------------------------------------------------
* 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.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* ifdef HAVE_CONFIG_H */
#include "parser.h"
#include "common.h"
#include "server.h"
#include "miau.h"
#include "perm.h"
#include "error.h"
#include "messages.h"
#include "chanlog.h"
#include "onconnect.h"
#include <errno.h>
#include <string.h>
static int virgin = 1; /* CFG_CHANNELS has effect only at start up. */
static int last_err_line;
static int line; /* Line we're processing. */
static int listid = CFG_NOLIST; /* List ID. */
/* Prototypes. */
void assign_int(int *target, const char *data, const int min);
void assign_boolean(int *target, const char *data);
void assign_param(char **target, char *source);
void parse_error(void);
char *trim(char *data, const int mode);
char *trimquotes(char *data);
void assign_option(int *target, const char *, char *);
int parse_option(const char *, char *);
/*
* Writes "source" over "target".
*
* Old data if freed and new data is trimmed.
*/
void
assign_param(char **target, char *source)
{
xfree(*target);
source = trim(source, SPACES);
if (source[0] == '\0') {
*target = NULL;
} else {
*target = xstrdup(source);
}
} /* void assign_param(char **target, char *source) */
static void
assign_param_no_trim(char **target, char *source)
{
xfree(*target);
if (source[0] == '\0') {
*target = NULL;
} else {
*target = xstrdup(source);
}
} /* static void assign_param_no_trim(char **target, char *source) */
/*
* Add server to server-list.
*/
void
add_server(const char *name, int port, const char *pass, int timeout)
{
server_type *server;
llist_node *node;
/* Must have a name for this server. */
if (name == NULL) { return; }
/* If port was not defined, use default. */
if (port == 0) { port = DEFAULT_PORT; }
/*
* We don't check if user has duplicate servers. If he does, he
* probably has a reason for that.
*/
server = (server_type *) xmalloc(sizeof(server_type));
server->name = xstrdup(name);
server->port = port;
server->password = (char *) ((pass == NULL) ? NULL : xstrdup(pass));
server->timeout = (timeout > 0) ? timeout : 0;
server->working = 1;
node = llist_create(server);
llist_add_tail(node, &servers.servers);
servers.amount++;
} /* void add_server(const char *name, int port, const char *pass,
int timeout) */
/*
* Remove quotes around *data.
*
* Return pointer to trimmed string or NULL if there are no quotes.
*/
char *
trimquotes(char *data)
{
int l;
l = strlen(data) - 1;
if (data[0] != '"' || data[l] != '"') {
parse_error(); /* Bad quoting. */
return NULL;
}
data[l] = '\0';
return data + 1;
} /* char *trimquotes(char *data) */
/*
* Remove useless characters like whitespaces and comments.
*
* Return pointer to trimmed string.
*/
char *
trim(char *data, const int mode)
{
int inside = 0;
char *ptr;
/* Skip whitespaces (minus linefeeds, they cannot exist). */
while (*data == ' ' || *data == '\t') { data++; }
if (mode == LINE) {
/* Remove comments. */
ptr = data;
while (*ptr != '\0') {
if (*ptr == '"') {
inside ^= 1;
} else if (*ptr == '#' && ! inside) {
*ptr = '\0';
break;
}
ptr++;
}
}
ptr = data + strlen(data) - 1;
/* Remove trailing whitespaces. */
while (ptr >= data && (*ptr == ' ' || *ptr == '\t')) {
*ptr = '\0';
ptr--;
}
return data;
} /* char *trim(char *data, const int mode) */
/*
* Get an integer out of data. Set it over target.
*/
void
assign_int(int *target, const char *data, const int min)
{
int n = min; /* Default to min. */
if (data != NULL) {
n = strtol(data, (char **) NULL, 10);
if (errno == ERANGE) {
parse_error();
return;
}
if (n < min) { n = min; }
}
*target = n;
} /* void assign_int(int *target, const char *data, const int min) */
/*
* Parse boolean value out of data.
*
* "true", "yes", "on", and "1" gives 1,
* "false, "no", off", and "0" gives 0,
* everything else prints an error.
*/
void
assign_boolean(int *target, const char *data)
{
if (parse_option(data, "true\0yes\0on\0""1\0\0") != -1) {
*target = 1;
} else if (parse_option(data, "false\0no\0off\0""0\0\0") != -1) {
*target = 0;
} else {
parse_error();
}
} /* void assign_boolean(int *target, const char *data) */
/*
* Assign option. See parse_option(...).
*
* This prints error if val was not found.
*/
void
assign_option(int *target, const char *val, char *options)
{
int t = parse_option(val, options);
if (t == -1) {
parse_error();
} else {
*target = t;
}
} /* void assign_option(int *target, const char *val, char *options) */
/*
* Parse option.
*
* Find "val" in "options", which is null-separated, (double) null-terminated
* list of possible options. Index of word in "options" will define returned
* value.
*
* Returns -1 if "val" is not found.
*/
int
parse_option(const char *val, char *options)
{
int i = 0;
/* Bad input means error in config. */
if (val == NULL || val[0] == '\0') {
return -1;
}
while (*options != '\0') {
if (xstrcmp(val, options) == 0) {
return i;
}
options = strchr(options, (int) '\0') + 1;
i++;
}
return -1;
} /* int parse_option(const char *val, char *options) */
/*
* Parse parameter definition that was already extracted from configuration
* file.
*/
void
parse_param(char *data)
{
char *t = strchr(data, '=');
char *val;
if (t == NULL) {
parse_error(); /* Didn't get mandatory '='-character. */
return;
}
*t = '\0';
data = trim(data, SPACES);
if (strchr(data, ' ') != NULL || strchr(data, '\t') != NULL) {
parse_error();
return;
}
val = trim(t + 1, SPACES);
if (*val == '{') {
/* Not expecting any other data. */
if (val[1] != '\0') { parse_error(); }
/* Resolve list-id. */
if (xstrcmp(data, "nicknames") == 0) { /* nicknames */
listid = CFG_NICKNAMES;
} else if (xstrcmp(data, "servers") == 0) { /* servers */
listid = CFG_SERVERS;
} else if (xstrcmp(data, "connhosts") == 0) { /* connhosts */
listid = CFG_CONNHOSTS;
} else if (xstrcmp(data, "ignore") == 0) { /* ignore */
listid = CFG_IGNORE;
#ifdef AUTOMODE
} else if (xstrcmp(data, "automodes") == 0) { /* automode */
listid = CFG_AUTOMODELIST;
#endif /* ifdef AUTOMODE */
#ifdef CHANLOG
} else if (xstrcmp(data, "chanlog") == 0) { /* chanlog */
listid = CFG_CHANLOG;
#endif /* ifdef CHANLOG */
} else if (xstrcmp(data, "channels") == 0) { /* channels */
listid = CFG_CHANNELS;
#ifdef ONCONNECT
} else if (xstrcmp(data, "onconnect") == 0) { /* onconnect */
listid = CFG_ONCONNECT;
#endif /* ifdef ONCONNECT */
} else {
listid = CFG_INVALID;
parse_error();
}
return;
}
val = trimquotes(val);
if (val == NULL) {
parse_error();
return;
}
if (xstrcmp(data, "realname") == 0) { /* realname */
assign_param(&cfg.realname, val);
} else if (xstrcmp(data, "username") == 0) { /* username */
assign_param(&cfg.username, val);
#ifdef NEED_CMDPASSWD
} else if (xstrcmp(data, "cmdpasswd") == 0) { /* cmdpasswd */
assign_param(&cfg.cmdpasswd, val);
#endif /* ifdef NEED_CMDPASSWD */
#ifdef QUICKLOG
} else if (xstrcmp(data, "qloglength") == 0) { /* qloglength */
assign_int(&cfg.qloglength, val, 0);
#ifdef QLOGSTAMP
} else if (xstrcmp(data, "timestamp") == 0) { /* timestamp */
/* See qlog.h for options' order. */
/* Double-terminate just to be sure. */
assign_option(&cfg.timestamp, val,
"none\0beginning\0end\0\0");
#endif /* ifdef QLOGSTAMP */
} else if (xstrcmp(data, "flushqlog") == 0) { /* flushqlog */
assign_boolean(&cfg.flushqlog, val);
} else if (xstrcmp(data, "autoqlog") == 0) { /* autoqlog */
assign_int(&cfg.autoqlog, val, -1);
#endif /* ifdef QUICKLOG */
#ifdef NEED_LOGGING
} else if (xstrcmp(data, "logsuffix") == 0 || /* logsuffix */
xstrcmp(data, "logpostfix") == 0) { /* TODO remove me */
assign_param(&cfg.logsuffix, val);
#endif /* ifdef NEED_LOGGING */
#ifdef INBOX
} else if (xstrcmp(data, "inbox") == 0) { /* inbox */
assign_boolean(&cfg.inbox, val);
#endif /* ifdef INBOX */
} else if (xstrcmp(data, "listenport") == 0) { /* listenport */
assign_int(&cfg.listenport, val, 0);
} else if (xstrcmp(data, "listenhost") == 0) { /* listenhost */
assign_param(&cfg.listenhost, val);
} else if (xstrcmp(data, "password") == 0) { /* password */
assign_param(&cfg.password, val);
} else if (xstrcmp(data, "leave") == 0) { /* leave */
assign_boolean(&cfg.leave, val);
} else if (xstrcmp(data, "chandiscon") == 0) { /* chandiscon */
/*
* 0 = nothing
* 1 = notice
* 2 = part
* 3 = privmsg
*/
assign_option(&cfg.chandiscon, val,
"nothing\0notice\0part\0privmsg\0\0");
} else if (xstrcmp(data, "leavemsg") == 0) { /* leavemsg */
assign_param(&cfg.leavemsg, val);
} else if (xstrcmp(data, "awaymsg") == 0) { /* awaymsg */
assign_param(&cfg.awaymsg, val);
} else if (xstrcmp(data, "usequitmsg") == 0) { /* usequitmsg */
assign_boolean(&cfg.usequitmsg, val);
} else if (xstrcmp(data, "autoaway") == 0) { /* autoaway */
/*
* 0 = never
* 1 = client detach
* 2 = no clients attached
*/
assign_option(&cfg.autoaway, val,
"never\0detach\0noclients\0\0");
} else if (xstrcmp(data, "getnick") == 0) { /* getnick */
/*
* 0 = never
* 1 = detached
* 2 = attached
* 3 = always
*
* Values are hardcoded all over miau.c.
* This should be fixed even though code is quite clear.
*/
/* Double-terminate just to be sure. */
assign_option(&cfg.getnick, val,
"never\0detached\0attached\0always\0\0");
} else if (xstrcmp(data, "getnickinterval") == 0) {/* getnickinterval */
assign_int(&cfg.getnickinterval, val, 0);
} else if (xstrcmp(data, "bind") == 0) { /* bind */
assign_param(&cfg.bind, val);
#ifdef AUTOMODE
} else if (xstrcmp(data, "automodedelay") == 0) { /* automodedelay */
assign_int(&cfg.automodedelay, val, 0);
#endif /* ifdef AUTOMODE */
} else if (xstrcmp(data, "antiidle") == 0) { /* antiide */
assign_int(&cfg.antiidle, val, 0);
} else if (xstrcmp(data, "nevergiveup") == 0) { /* nevergiveup */
assign_boolean(&cfg.nevergiveup, val);
} else if (xstrcmp(data, "norestricted") == 0) { /* norestricted */
assign_boolean(&cfg.jumprestricted, val);
} else if (xstrcmp(data, "stonedtimeout") == 0) { /* stonedtimeout*/
assign_int(&cfg.stonedtimeout, val, MINSTONEDTIMEOUT);
} else if (xstrcmp(data, "connecttimeout") == 0) { /* connecttimeout */
assign_int(&cfg.connecttimeout, val, MINCONNECTTIMEOUT);
} else if (xstrcmp(data, "reconnectdelay") == 0) { /* reconnectdelay */
assign_int(&cfg.reconnectdelay, val, MINRECONNECTDELAY);
} else if (xstrcmp(data, "rejoin") == 0) { /* rejoin */
assign_boolean(&cfg.rejoin, val);
} else if (xstrcmp(data, "forwardmsg") == 0) { /* forwardmsg */
assign_param(&cfg.forwardmsg, val);
} else if (xstrcmp(data, "forwardtime") == 0) { /* forwardtime */
assign_int(&cfg.forwardtime, val, 30);
} else if (xstrcmp(data, "maxclients") == 0) { /* maxclients */
assign_int(&cfg.maxclients, val, 1);
#ifdef PRIVLOG
} else if (xstrcmp(data, "privlog") == 0) { /* privlog */
/* See log.h for options' order. */
/* Double-terminate just to be sure. */
assign_option(&cfg.privlog, val,
"never\0detached\0attached\0always\0\0");
#endif /* ifdef PRIVLOG */
#ifdef DCCBOUNCE
} else if (xstrcmp(data, "dccbounce") == 0) { /* dccbounce */
assign_boolean(&cfg.dccbounce, val);
} else if (xstrcmp(data, "dccbindhost") == 0) {
assign_param(&cfg.dccbindhost, val);
#endif /* ifdef DCCBOUNCE */
} else if (xstrcmp(data, "nickfillchar") == 0) { /* nickfillchar */
cfg.nickfillchar = val[0];
} else if (xstrcmp(data, "usermode") == 0) { /* usermode */
assign_param(&cfg.usermode, val);
} else if (xstrcmp(data, "maxnicklen") == 0) { /* maxnicklen */
assign_int(&cfg.maxnicklen, val, 3);
} else if (xstrcmp(data, "floodtimer") == 0) { /* floodtimer */
assign_int(&cfg.floodtimer, val, 0);
} else if (xstrcmp(data, "burstsize") == 0) { /* burstsize */
assign_int(&cfg.burstsize, val, 1);
} else if (xstrcmp(data, "jointries") == 0) { /* jointries */
assign_int(&cfg.jointries, val, 0);
} else if (xstrcmp(data, "statelog") == 0) { /* statelog */
assign_boolean(&cfg.statelog, val);
} else if (xstrcmp(data, "noidentifycapab") == 0) {
assign_boolean(&cfg.no_identify_capab, val);
} else if (xstrcmp(data, "qlog_no_my_quit") == 0) {
assign_boolean(&cfg.qlog_no_my_quit, val);
} else if (xstrcmp(data, "privmsg_format") == 0) {
if (val != NULL && strstr(val, "%s") != NULL) {
assign_param_no_trim(&cfg.privmsg_fmt, val);
} else {
parse_error();
}
} else if (xstrcmp(data, "newserv_disconn") == 0) {
/* keep in sync with enum in miau.h! */
assign_option(&cfg.newserv_disconn, val,
"never\0newserver\0always\0\0");
} else {
parse_error();
}
} /* void parse_param(char *data) */
/*
* Parse list-item.
*/
void
parse_list_line(char *data)
{
permlist_type *permlist = NULL;
char **param;
int paramcount = 0;
int n;
int eol = 0;
int ok = 0;
int inside = 0; /* Inside quotes. */
char *ptr;
char *par;
#ifdef CHANLOG
int logtype;
#endif /* ifdef CHANLOG */
/* Starting a new list, eh ? */
if (strchr(data, '=') != NULL && strchr(data, '{') != NULL) {
parse_error();
listid = CFG_INVALID;
parse_param(data);
return;
}
/* Ending a list. */
if (data[0] == '}') {
/* No trailing data, thank you. */
if (data[1] != '\0') { parse_error(); }
listid = CFG_NOLIST;
return;
}
/* Read parameters. */
/* We can't use strtok(), because it eats subsequent delimeters. */
param = (char **) xmalloc((sizeof(char *)) * MAXNUMOFPARAMS);
ptr = data;
par = ptr;
/* Initially set all parameters to NULL. */
for (n = 0; n < MAXNUMOFPARAMS; n++) {
param[n] = NULL;
}
/*
* This is ugly; we parse line until '\0', which _breaks out_ from
* while(1)-loop...
*/
do {
/* Toggle "inside quotes" -status and remove quotes. */
if (*ptr == '"') {
inside++;
}
/* End of line ? */
else if (*ptr == '\0') {
eol = 1;
}
/* Got end of parameter. */
if ((*ptr == ':' || *ptr == '\0') &&
(inside == 0 || inside == 2)) {
*ptr = '\0';
par = trim(par, SPACES);
if (strlen(par) > 0) {
if (par[strlen(par) - 1] != '"' ||
par[0] != '"') {
parse_error();
return;
}
par++;
par[strlen(par) - 1] = '\0';
/* Ok, got our parameter. */
param[paramcount] = xstrdup(par);
}
paramcount++;
par = ptr + 1;
inside = 0;
}
ptr++;
} while (eol == 0);
/* If still inside quotes, the line was bad. */
if (inside) {
parse_error();
return;
}
/* We want at least one parameter. */
if (paramcount == 0) {
parse_error();
return;
}
/* Process parameters. */
switch (listid) {
case CFG_NICKNAMES:
if (paramcount != 1) {
break;
}
llist_add_tail(llist_create(xstrdup(param[0])),
&nicknames.nicks);
ok = 1;
break;
case CFG_SERVERS:
if (paramcount > 4) {
break;
}
{
int t0, t1;
t0 = t1 = 0; /* keep some compilers happy */
assign_int(&t0, param[1], 0);
assign_int(&t1, param[3], 0);
add_server(param[0], t0, param[2], t1);
ok = 1;
}
break;
case CFG_CONNHOSTS:
if (paramcount > 2) {
break;
}
permlist = &connhostlist;
ok = 1;
break;
case CFG_IGNORE:
if (paramcount > 2) {
break;
}
permlist = &ignorelist;
ok = 1;
break;
#ifdef AUTOMODE
case CFG_AUTOMODELIST:
if (paramcount > 2 || param[0][1] != ':') {
break;
}
permlist = &automodelist;
ok = 1;
break;
#endif /* ifdef AUTOMODE */
#ifdef CHANLOG
case CFG_CHANLOG:
if (paramcount < 2 || paramcount > 3) {
break;
}
logtype = 0;
ptr = param[1];
while (*ptr != '\0') {
switch (*ptr) {
case LOG_MESSAGE_C:
logtype |= LOG_MESSAGE;
break;
case LOG_JOIN_C:
logtype |= LOG_JOIN;
break;
case LOG_PART_C:
logtype |= LOG_PART;
break;
case LOG_QUIT_C:
logtype |= LOG_QUIT;
break;
case LOG_MODE_C:
logtype |= LOG_MODE;
break;
case LOG_NICK_C:
logtype |= LOG_NICK;
break;
case LOG_MISC_C:
logtype |= LOG_MISC;
break;
case LOG_MIAU_C:
logtype |= LOG_MIAU;
break;
case LOG_ALL_C:
logtype |= LOG_ALL;
break;
case LOG_ATTACHED_C:
logtype |= LOG_ATTACHED;
break;
case LOG_DETACHED_C:
logtype |= LOG_DETACHED;
break;
case LOG_CONTIN_C:
logtype |= LOG_CONTIN;
break;
default:
parse_error();
break;
}
ptr++;
}
/*
* If no LOG_ATTACHED nor LOG_DETACHED was
* defined, use default: LOG_CONTIN
*/
if (! (logtype & LOG_ATTACHED) &&
! (logtype & LOG_DETACHED)) {
logtype |= LOG_CONTIN;
}
chanlog_add_rule(param[0], param[2], logtype);
ok = 1;
break;
#endif /* ifdef CHANLOG */
case CFG_CHANNELS:
if (paramcount > 2) {
break;
}
/* CFG_CHANNELS only has effect at start up. */
if (virgin == 1) {
channel_type *channel;
if (param[0] == NULL || param[0][0] == '\0') {
break;
}
/*
* Make sure there are no channels such as
* !#foo in miaurc. Auto-creation of safe
* channels is a Bad Idea(tm) and we don't
* want to help with that. If user wants to
* define "real" channel name of a safe channel,
* we won't stop him from hurting himself.
*/
if (param[0][0] == '!' && param[0][1] == '#') {
break;
}
/* Not adding same channel twice. */
if (channel_find(param[0], LIST_PASSIVE)
!= NULL) {
break;
}
/* channel_add will set up us a key. */
channel = channel_add(param[0],
param[1], LIST_PASSIVE);
}
ok = 1;
break;
#ifdef ONCONNECT
case CFG_ONCONNECT:
if (paramcount > 3) {
break;
}
if (*param[0] == 'p' || *param[0] == 'n' ||
*param[0] == 'r') {
onconnect_add(*(param[0]), param[1], param[2]);
ok = 1;
}
break;
#endif /* ifdef ONCONNECT */
case CFG_INVALID:
ok = 1;
break;
}
/* Did we make it? */
if (ok == 0) {
parse_error();
} else if (permlist != NULL) {
/* Everything went just fine and there's something to do... */
if (paramcount == 1) {
add_perm(permlist, xstrdup(param[0]), 1);
} else if (paramcount == 2) {
assign_boolean(&n, param[1]);
add_perm(permlist, xstrdup(param[0]), n);
}
}
/* Free parameters. */
while (paramcount > 0) {
paramcount--;
xfree(param[paramcount]);
}
xfree(param);
} /* void parse_list_line(char *data) */
/*
* Parse configuration file.
*/
int
parse_cfg(const char *cfgfile)
{
FILE *file;
int filelen;
char *buf;
char *bufptr;
char *nextptr;
buf = (char *) xmalloc(READBUFSIZE);
line = 1;
last_err_line = -1;
file = fopen(cfgfile, "r");
if (file == NULL) {
return -1;
}
/* Make sure configuration-file ends. */
filelen = (int) fread(buf, 1, READBUFSIZE - 2, file);
buf[filelen] = '\n';
buf[filelen + 1] = '\0';
bufptr = buf;
nextptr = strchr(buf, '\n');
while (nextptr >= bufptr) {
*nextptr = '\0';
if (*(nextptr - 1) == '\r') {
*(nextptr - 1) = '\0';
}
bufptr = trim(bufptr, LINE);
if (strlen(bufptr) > 0 && *bufptr != '#') {
if (listid == CFG_NOLIST) {
parse_param(bufptr);
} else {
parse_list_line(bufptr);
}
}
bufptr = nextptr + 1;
nextptr = strchr(bufptr, '\n');
line++;
}
if (listid != CFG_NOLIST) {
parse_error(); /* Unfinished list. */
}
fclose(file);
xfree(buf);
virgin = 0; /* Lost virginity. ;-) */
return 0;
} /* int parse_cfg(const char *cfgfile) */
void
parse_error(void)
{
if (line != last_err_line) {
error(PARSE_SE, line);
last_err_line = line;
}
} /* void parse_error(void) */
syntax highlighted by Code2HTML, v. 0.9.1