/* * ------------------------------------------------------- * Copyright (C) 2003-2007 Tommi Saviranta * ------------------------------------------------------- * 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 #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 #include 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) */