pissircd/src/updconf.c

1731 lines
43 KiB
C

/*
* Configuration file updater - upgrade from 3.2.x to 4.x
* (C) Copyright 2015 Bram Matthys and the UnrealIRCd team
*
* License: GPLv2
*/
#include "unrealircd.h"
extern void config_free(ConfigFile *cfptr);
char configfiletmp[512];
struct Upgrade
{
char *locop_host;
char *oper_host;
char *coadmin_host;
char *admin_host;
char *sadmin_host;
char *netadmin_host;
int host_on_oper_up;
};
struct Upgrade upgrade;
typedef struct FlagMapping FlagMapping;
struct FlagMapping
{
char shortflag;
char *longflag;
};
static FlagMapping FlagMappingTable[] = {
{ 'o', "local" },
{ 'O', "global" },
{ 'r', "can_rehash" },
{ 'D', "can_die" },
{ 'R', "can_restart" },
{ 'w', "can_wallops" },
{ 'g', "can_globops" },
{ 'c', "can_localroute" },
{ 'L', "can_globalroute" },
{ 'K', "can_globalkill" },
{ 'b', "can_kline" },
{ 'B', "can_unkline" },
{ 'n', "can_localnotice" },
{ 'G', "can_globalnotice" },
{ 'A', "admin" },
{ 'a', "services-admin" },
{ 'N', "netadmin" },
{ 'C', "coadmin" },
{ 'z', "can_zline" },
{ 'W', "get_umodew" },
{ 'H', "get_host" },
{ 't', "can_gkline" },
{ 'Z', "can_gzline" },
{ 'v', "can_override" },
{ 'q', "can_setq" },
{ 'd', "can_dccdeny" },
{ 'T', "can_tsctl" },
{ 0, NULL },
};
int needs_modules_default_conf = 1;
int needs_operclass_default_conf = 1;
static void die()
{
#ifdef _WIN32
win_error(); /* ? */
#endif
exit(0);
}
#define CFGBUFSIZE 1024
void modify_file(int start, char *ins, int stop)
{
char configfiletmp2[512];
FILE *fdi, *fdo;
char *rdbuf = NULL, *wbuf;
int n;
int first = 1;
snprintf(configfiletmp2, sizeof(configfiletmp2), "%s.tmp", configfiletmp); // .tmp.tmp :D
#ifndef _WIN32
fdi = fopen(configfiletmp, "r");
fdo = fopen(configfiletmp2, "w");
#else
fdi = fopen(configfiletmp, "rb");
fdo = fopen(configfiletmp2, "wb");
#endif
if (!fdi || !fdo)
{
config_error("could not read/write to %s/%s", configfiletmp, configfiletmp2);
die();
}
rdbuf = safe_alloc(start);
if ((n = fread(rdbuf, 1, start, fdi)) != start)
{
config_error("read error in remove_section(%d,%d): %d", start, stop, n);
die();
}
fwrite(rdbuf, 1, start, fdo);
safe_free(rdbuf);
if (ins)
fwrite(ins, 1, strlen(ins), fdo); /* insert this piece */
if (stop > 0)
{
if (fseek(fdi, stop+1, SEEK_SET) != 0)
goto end; /* end of file we hope.. */
}
// read the remaining stuff
rdbuf = safe_alloc(CFGBUFSIZE);
while(1)
{
n = fread(rdbuf, 1, CFGBUFSIZE, fdi);
if (n <= 0)
break; // done
wbuf = rdbuf;
if (first && (stop > 0))
{
if ((n > 0) && (*wbuf == '\r'))
{
wbuf++;
n--;
}
if ((n > 0) && (*wbuf == '\n'))
{
wbuf++;
n--;
}
first = 0;
if (n <= 0)
break; /* we are done (EOF) */
}
fwrite(wbuf, 1, n, fdo);
}
end:
fclose(fdi);
fclose(fdo);
safe_free(rdbuf);
// todo: handle write errors and such..
unlink(configfiletmp);
if (rename(configfiletmp2, configfiletmp) < 0)
{
config_error("Could not rename '%s' to '%s': %s", configfiletmp2, configfiletmp, strerror(errno));
die();
}
}
void remove_section(int start, int stop)
{
modify_file(start, NULL, stop);
}
void insert_section(int start, char *buf)
{
#ifdef _WIN32
static char realbuf[16384];
char *i, *o;
if (strlen(buf) > ((sizeof(realbuf)/2)-2))
abort(); /* damn lazy you !!! */
for (i = buf, o = realbuf; *i; i++)
{
if (*i == '\n')
{
*o++ = '\r';
*o++ = '\n';
} else
{
*o++ = *i;
}
}
*o = '\0';
modify_file(start, realbuf, 0);
#else
modify_file(start, buf, 0);
#endif
}
void replace_section(ConfigEntry *ce, char *buf)
{
remove_section(ce->ce_fileposstart, ce->ce_fileposend);
insert_section(ce->ce_fileposstart, buf);
}
static char buf[8192];
int upgrade_me_block(ConfigEntry *ce)
{
ConfigEntry *cep;
char *name = NULL;
char *info = NULL;
int numeric = 0;
char sid[16];
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!strcmp(cep->ce_varname, "sid"))
return 0; /* no upgrade needed */
else if (!cep->ce_vardata)
{
config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
"me", cep->ce_varname);
return 0;
}
else if (!strcmp(cep->ce_varname, "name"))
name = cep->ce_vardata;
else if (!strcmp(cep->ce_varname, "info"))
info = cep->ce_vardata;
else if (!strcmp(cep->ce_varname, "numeric"))
numeric = atoi(cep->ce_vardata);
}
if (!name || !info || !numeric)
{
/* Invalid block as it does not contain the 3.2.x mandatory items */
return 0;
}
snprintf(sid, sizeof(sid), "%.3d", numeric);
snprintf(buf, sizeof(buf),
"me {\n"
"\tname %s;\n"
"\tinfo \"%s\";\n"
"\tsid %s;\n"
"};\n",
name,
info,
sid);
replace_section(ce, buf);
config_status("- me block upgraded");
return 1;
}
int upgrade_link_block(ConfigEntry *ce)
{
ConfigEntry *cep, *cepp;
char *bind_ip = NULL;
char *username = NULL;
char *hostname = NULL;
char *port = NULL;
char *password_receive = NULL;
char *password_connect = NULL;
char *class = NULL;
int options_ssl = 0;
int options_autoconnect = 0;
int options_nohostcheck = 0;
int options_quarantine = 0;
/* options_nodnscache is deprecated, always now.. */
char *hub = NULL;
char *leaf = NULL;
int leaf_depth = -1;
char *ciphers = NULL;
char *password_receive_authmethod = NULL;
int need_incoming = 0, need_outgoing = 0;
/* ripped from test_link */
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!strcmp(cep->ce_varname, "incoming") || !strcmp(cep->ce_varname, "outgoing"))
return 0; /* no upgrade needed */
else if (!strcmp(cep->ce_varname, "options"))
{
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
{
if (!strcmp(cepp->ce_varname, "ssl"))
options_ssl = 1;
if (!strcmp(cepp->ce_varname, "autoconnect"))
options_autoconnect = 1;
if (!strcmp(cepp->ce_varname, "nohostcheck"))
options_nohostcheck = 1;
if (!strcmp(cepp->ce_varname, "quarantine"))
options_quarantine = 1;
}
}
else if (!cep->ce_vardata)
{
config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
"link", cep->ce_varname);
return 0;
}
else if (!strcmp(cep->ce_varname, "username"))
username = cep->ce_vardata;
else if (!strcmp(cep->ce_varname, "hostname"))
hostname = cep->ce_vardata;
else if (!strcmp(cep->ce_varname, "bind-ip"))
bind_ip = cep->ce_vardata;
else if (!strcmp(cep->ce_varname, "port"))
port = cep->ce_vardata;
else if (!strcmp(cep->ce_varname, "password-receive"))
{
password_receive = cep->ce_vardata;
if (cep->ce_entries)
password_receive_authmethod = cep->ce_entries->ce_varname;
}
else if (!strcmp(cep->ce_varname, "password-connect"))
password_connect = cep->ce_vardata;
else if (!strcmp(cep->ce_varname, "class"))
class = cep->ce_vardata;
else if (!strcmp(cep->ce_varname, "hub"))
hub = cep->ce_vardata;
else if (!strcmp(cep->ce_varname, "leaf"))
leaf = cep->ce_vardata;
else if (!strcmp(cep->ce_varname, "leafdepth"))
leaf_depth = atoi(cep->ce_vardata);
else if (!strcmp(cep->ce_varname, "ciphers"))
ciphers = cep->ce_vardata;
}
if (!username || !hostname || !class || !password_receive ||
!password_connect || !port)
{
/* Invalid link block as it does not contain the 3.2.x mandatory items */
return 0;
}
if (strchr(hostname, '?') || strchr(hostname, '*'))
{
/* Wildcards in hostname: incoming only */
need_incoming = 1;
need_outgoing = 0;
}
else
{
/* IP (or hostname with nohostcheck) */
need_incoming = 1;
need_outgoing = 1;
}
snprintf(buf, sizeof(buf), "link %s {\n", ce->ce_vardata);
if (need_incoming)
{
char upg_mask[HOSTLEN+USERLEN+8];
if (options_nohostcheck)
{
strlcpy(upg_mask, "*", sizeof(upg_mask));
}
else
{
if (!strcmp(username, "*"))
strlcpy(upg_mask, hostname, sizeof(upg_mask)); /* just host */
else
snprintf(upg_mask, sizeof(upg_mask), "%s@%s", username, hostname); /* user@host */
}
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tincoming {\n\t\tmask %s;\n\t};\n", upg_mask);
}
if (need_outgoing)
{
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
"\toutgoing {\n"
"\t\tbind-ip %s;\n"
"\t\thostname %s;\n"
"\t\tport %s;\n"
"\t\toptions { %s%s};\n"
"\t};\n",
bind_ip,
hostname,
port,
options_ssl ? "ssl; " : "",
options_autoconnect ? "autoconnect; " : "");
}
if (strcasecmp(password_connect, password_receive))
{
if (!password_receive_authmethod)
{
/* Prompt user ? */
config_warn("Link block '%s' has a different connect/receive password. "
"This is no longer supported in UnrealIRCd 4.x",
ce->ce_vardata);
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
"\tpassword \"%s\"; /* WARNING: password changed due to 4.x upgrade */\n",
options_autoconnect ? password_connect : password_receive);
} else
{
/* sslcertificate or sslcertficatefp */
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
"\tpassword \"%s\" { %s; };\n",
password_receive,
password_receive_authmethod);
}
} else {
/* identical connect & receive passwords. easy. */
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
"\tpassword \"%s\";\n", password_receive);
}
if (hub)
{
if (strcmp(hub, "*")) // only if it's something other than *, as * is the default anyway..
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\thub %s;\n", hub);
} else
if (leaf)
{
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tleaf %s;\n", leaf);
if (leaf_depth)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tleaf-depth %d;\n", leaf_depth);
}
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tclass %s;\n", class);
if (ciphers)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tciphers %s;\n", ciphers);
if (options_quarantine)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\toptions { quarantine; };\n");
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "};\n"); /* end */
replace_section(ce, buf);
config_status("- link block '%s' upgraded", ce->ce_vardata);
return 1;
}
/** oper::from::userhost becomes oper::mask & vhost::from::userhost becomes vhost::mask */
#define MAXFROMENTRIES 100
int upgrade_from_subblock(ConfigEntry *ce)
{
ConfigEntry *cep;
char *list[MAXFROMENTRIES];
int listcnt = 0;
memset(list, 0, sizeof(list));
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!cep->ce_vardata)
continue;
else if (!strcmp(cep->ce_varname, "userhost"))
{
if (listcnt == MAXFROMENTRIES)
break; // no room, sorry.
list[listcnt++] = cep->ce_vardata;
}
}
if (listcnt == 0)
return 0; /* invalid block. strange. */
if (listcnt == 1)
{
char *m = !strncmp(list[0], "*@", 2) ? list[0]+2 : list[0]; /* skip or don't skip the user@ part */
snprintf(buf, sizeof(buf), "mask %s;\n", m);
} else
{
/* Multiple (list of masks) */
int i;
snprintf(buf, sizeof(buf), "mask {\n");
for (i=0; i < listcnt; i++)
{
char *m = !strncmp(list[i], "*@", 2) ? list[i]+2 : list[i]; /* skip or don't skip the user@ part */
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\t%s;\n", m);
}
strlcat(buf, "\t};\n", sizeof(buf));
}
replace_section(ce, buf);
config_status("- %s::from::userhost sub-block upgraded", ce->ce_prevlevel ? ce->ce_prevlevel->ce_varname : "???");
return 1;
}
int upgrade_loadmodule(ConfigEntry *ce)
{
char *file = ce->ce_vardata;
char tmp[512], *p, *newfile;
if (!file)
return 0;
if (our_strcasestr(file, "commands.dll") || our_strcasestr(file, "/commands.so"))
{
snprintf(buf, sizeof(buf), "include \"modules.default.conf\";\n");
needs_modules_default_conf = 0;
if (needs_operclass_default_conf)
{
/* This is a nice place :) */
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "include \"operclass.default.conf\";\n");
needs_operclass_default_conf = 0;
}
replace_section(ce, buf);
config_status("- loadmodule for '%s' replaced with an include \"modules.default.conf\"", file);
return 1;
}
if (our_strcasestr(file, "cloak.dll") || our_strcasestr(file, "/cloak.so"))
{
replace_section(ce, "/* NOTE: cloaking module is included in modules.default.conf */");
config_status("- loadmodule for '%s' removed as this is now in modules.default.conf", file);
return 1;
}
/* All other loadmodule commands... */
strlcpy(tmp, file, sizeof(tmp));
p = strstr(tmp, ".so");
if (p)
*p = '\0';
p = our_strcasestr(tmp, ".dll");
if (p)
*p = '\0';
newfile = !strncmp(tmp, "src/", 4) ? tmp+4 : tmp;
newfile = !strncmp(newfile, "modules/", 8) ? newfile+8 : newfile;
if (!strcmp(newfile, file))
return 0; /* no change */
snprintf(buf, sizeof(buf), "loadmodule \"%s\";\n", newfile);
replace_section(ce, buf);
config_status("- loadmodule line converted to new syntax");
return 1;
}
int upgrade_include(ConfigEntry *ce)
{
char *file = ce->ce_vardata;
static int badwords_upgraded_already = 0;
if (!file)
return 0;
if (!strstr(file, "help/") && match_simple("help*.conf", file))
{
snprintf(buf, sizeof(buf), "include \"help/%s\";\n", file);
replace_section(ce, buf);
config_status("- include for '%s' replaced with 'help/%s'", file, file);
return 1;
}
if (!strcmp("badwords.quit.conf", file))
{
*buf = '\0';
replace_section(ce, buf);
config_status("- include for '%s' removed (now in badwords.conf)", file);
return 1;
}
if (match_simple("badwords.*.conf", file))
{
if (badwords_upgraded_already)
{
*buf = '\0';
config_status("- include for '%s' removed (now in badwords.conf)", file);
} else {
strcpy(buf, "/* all badwords are now in badwords.conf */\ninclude \"badwords.conf\";\n");
badwords_upgraded_already = 1;
config_status("- include for '%s' replaced with 'badwords.conf'", file);
}
replace_section(ce, buf);
return 1;
}
return 0;
}
#define MAXSPFTARGETS 32
int upgrade_spamfilter_block(ConfigEntry *ce)
{
ConfigEntry *cep, *cepp;
char *reason = NULL;
char *regex = NULL;
char *action = NULL;
char *ban_time = NULL;
char *target[MAXSPFTARGETS];
char targets[512];
int targetcnt = 0;
char *match_type = NULL;
char *p;
memset(target, 0, sizeof(target));
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!strcmp(cep->ce_varname, "match") || !strcmp(cep->ce_varname, "match-type"))
return 0; /* no upgrade needed */
else if (!strcmp(cep->ce_varname, "target"))
{
if (cep->ce_vardata)
{
target[0] = cep->ce_vardata;
}
else if (cep->ce_entries)
{
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
{
if (targetcnt == MAXSPFTARGETS)
break;
target[targetcnt++] = cepp->ce_varname;
}
}
}
else if (!cep->ce_vardata)
continue; /* invalid */
else if (!strcmp(cep->ce_varname, "regex"))
{
regex = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "action"))
{
action = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "reason"))
{
reason = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "ban-time"))
{
ban_time = cep->ce_vardata;
}
}
if (!regex || !target[0] || !action)
return 0; /* invalid spamfilter block */
/* build target(s) list */
if (targetcnt > 1)
{
int i;
strlcpy(targets, "{ ", sizeof(targets));
for (i=0; i < targetcnt; i++)
{
snprintf(targets+strlen(targets), sizeof(targets)-strlen(targets),
"%s; ", target[i]);
}
strlcat(targets, "}", sizeof(target));
} else {
strlcpy(targets, target[0], sizeof(targets));
}
/* Determine match-type, fallback to 'posix' (=3.2.x regex engine) */
match_type = "simple";
for (p = regex; *p; p++)
if (!isalnum(*p) && !isspace(*p))
{
match_type = "posix";
break;
}
snprintf(buf, sizeof(buf), "spamfilter {\n"
"\tmatch-type %s;\n"
"\tmatch \"%s\";\n"
"\ttarget %s;\n"
"\taction %s;\n",
match_type,
unreal_add_quotes(regex),
targets,
action);
/* optional: reason */
if (reason)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\treason \"%s\";\n", unreal_add_quotes(reason));
/* optional: ban-time */
if (ban_time)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tban-time \"%s\";\n", ban_time);
strlcat(buf, "};\n", sizeof(buf));
replace_section(ce, buf);
config_status("- spamfilter block converted to new syntax");
return 1;
}
#define MAXOPTIONS 32
int upgrade_allow_block(ConfigEntry *ce)
{
ConfigEntry *cep, *cepp;
char *hostname = NULL;
char *ip = NULL;
char *maxperip = NULL;
char *ipv6_clone_mask = NULL;
char *password = NULL;
char *password_type = NULL;
char *class = NULL;
char *redirect_server = NULL;
int redirect_port = 0;
char *options[MAXOPTIONS];
int optionscnt = 0;
char options_str[512], comment[512];
memset(options, 0, sizeof(options));
*comment = *options_str = '\0';
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!strcmp(cep->ce_varname, "options"))
{
if (cep->ce_vardata)
{
options[0] = cep->ce_vardata;
optionscnt = 1;
}
else if (cep->ce_entries)
{
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
{
if (optionscnt == MAXOPTIONS)
break;
options[optionscnt++] = cepp->ce_varname;
}
}
}
else if (!cep->ce_vardata)
continue; /* invalid */
else if (!strcmp(cep->ce_varname, "hostname"))
{
hostname = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "ip"))
{
ip = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "maxperip"))
{
maxperip = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "ipv6-clone-mask"))
{
ipv6_clone_mask = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "password"))
{
password = cep->ce_vardata;
if (cep->ce_entries)
password_type = cep->ce_entries->ce_varname;
}
else if (!strcmp(cep->ce_varname, "class"))
{
class = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "redirect-server"))
{
redirect_server = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "redirect-port"))
{
redirect_port = atoi(cep->ce_vardata);
}
}
if (!ip || !hostname || !class)
return 0; /* missing 3.2.x items in allow block, upgraded already! (or invalid) */
/* build target(s) list */
if (optionscnt == 0)
{
*options_str = '\0';
}
else
{
int i;
for (i=0; i < optionscnt; i++)
{
snprintf(options_str+strlen(options_str), sizeof(options_str)-strlen(options_str),
"%s; ", options[i]);
}
}
/* drop either 'ip' or 'hostname' */
if (!strcmp(ip, "*@*") && !strcmp(hostname, "*@*"))
hostname = NULL; /* just ip */
else if (strstr(ip, "NOMATCH"))
ip = NULL;
else if (strstr(hostname, "NOMATCH"))
hostname = NULL;
else if (!strchr(hostname, '.') && strcmp(hostname, "localhost"))
hostname = NULL;
else if (!strchr(ip, '.'))
ip = NULL;
else
{
/* very rare case -- let's bet on IP */
snprintf(comment, sizeof(comment), "/* CHANGED BY 3.2.x TO 4.x CONF UPGRADE!! Was: ip %s; hostname %s; */\n", ip, hostname);
hostname = NULL;
}
snprintf(buf, sizeof(buf), "allow {\n");
if (*comment)
strlcat(buf, comment, sizeof(buf));
if (ip)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tip \"%s\";\n", ip);
if (hostname)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\thostname \"%s\";\n", hostname);
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tclass %s;\n", class);
/* maxperip: optional in 3.2.x, mandatory in 4.x */
if (maxperip)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tmaxperip %s;\n", maxperip);
else
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tmaxperip 3; /* CHANGED BY 3.2.x TO 4.x CONF UPGRADE! */\n");
if (ipv6_clone_mask)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tipv6-clone-mask %s;\n", ipv6_clone_mask);
if (password)
{
if (password_type)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tpassword \"%s\" { %s; };\n", password, password_type);
else
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tpassword \"%s\";\n", password);
}
if (redirect_server)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tredirect-server %s;\n", redirect_server);
if (redirect_port)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tredirect-port %d;\n", redirect_port);
if (*options_str)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\toptions { %s};\n", options_str);
strlcat(buf, "};\n", sizeof(buf));
replace_section(ce, buf);
config_status("- allow block converted to new syntax");
return 1;
}
/* Pick out the ip address and the port number from a string.
* The string syntax is: ip:port. ip must be enclosed in brackets ([]) if its an ipv6
* address because they contain colon (:) separators. The ip part is optional. If the string
* contains a single number its assumed to be a port number.
*
* Returns with ip pointing to the ip address (if one was specified), a "*" (if only a port
* was specified), or an empty string if there was an error. port is returned pointing to the
* port number if one was specified, otherwise it points to a empty string.
*/
void ipport_separate(char *string, char **ip, char **port)
{
char *f;
/* assume failure */
*ip = *port = "";
/* sanity check */
if (string && strlen(string) > 0)
{
/* handle ipv6 type of ip address */
if (*string == '[')
{
if ((f = strrchr(string, ']')))
{
*ip = string + 1; /* skip [ */
*f = '\0'; /* terminate the ip string */
/* next char must be a : if a port was specified */
if (*++f == ':')
{
*port = ++f;
}
}
}
/* handle ipv4 and port */
else if ((f = strchr(string, ':')))
{
/* we found a colon... we may have ip:port or just :port */
if (f == string)
{
/* we have just :port */
*ip = "*";
}
else
{
/* we have ip:port */
*ip = string;
*f = '\0';
}
*port = ++f;
}
/* no ip was specified, just a port number */
else if (!strcmp(string, my_itoa(atoi(string))))
{
*ip = "*";
*port = string;
}
}
}
int upgrade_listen_block(ConfigEntry *ce)
{
ConfigEntry *cep, *cepp;
char *ip = NULL;
char *port = NULL;
char *options[MAXOPTIONS];
int optionscnt = 0;
char options_str[512];
char copy[128];
memset(options, 0, sizeof(options));
*options_str = '\0';
if (!ce->ce_vardata)
return 0; /* already upgraded */
strlcpy(copy, ce->ce_vardata, sizeof(copy));
ipport_separate(copy, &ip, &port);
if (!ip || !*ip || !port || !*port)
return 0; /* invalid conf */
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!strcmp(cep->ce_varname, "options"))
{
if (cep->ce_entries)
{
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
{
if (optionscnt == MAXOPTIONS)
break;
options[optionscnt++] = cepp->ce_varname;
}
}
}
}
/* build options list */
if (optionscnt == 0)
{
*options_str = '\0';
}
else
{
int i;
for (i=0; i < optionscnt; i++)
{
snprintf(options_str+strlen(options_str), sizeof(options_str)-strlen(options_str),
"%s; ", options[i]);
}
}
snprintf(buf, sizeof(buf), "listen {\n"
"\tip %s;\n"
"\tport %s;\n",
ip,
port);
if (*options_str)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\toptions { %s};\n", options_str);
strlcat(buf, "};\n", sizeof(buf));
replace_section(ce, buf);
config_status("- listen block converted to new syntax");
return 1;
}
int upgrade_cgiirc_block(ConfigEntry *ce)
{
ConfigEntry *cep;
char *type = NULL;
char *username = NULL;
char *hostname = NULL;
char *password = NULL, *password_type = NULL;
char mask[USERLEN+HOSTLEN+8];
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!cep->ce_vardata)
{
config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
"cgiirc", cep->ce_varname);
return 0;
}
else if (!strcmp(cep->ce_varname, "type"))
type = cep->ce_vardata;
else if (!strcmp(cep->ce_varname, "username"))
username = cep->ce_vardata;
else if (!strcmp(cep->ce_varname, "hostname"))
hostname = cep->ce_vardata;
else if (!strcmp(cep->ce_varname, "password"))
{
password = cep->ce_vardata;
if (cep->ce_entries)
password_type = cep->ce_entries->ce_varname;
}
}
if (!type || !hostname)
{
/* Invalid block as it does not contain the 3.2.x mandatory items */
return 0;
}
if (username)
snprintf(mask, sizeof(mask), "%s@%s", username, hostname);
else
strlcpy(mask, hostname, sizeof(mask));
if (!strcmp(type, "old"))
{
snprintf(buf, sizeof(buf),
"webirc {\n"
"\ttype old;\n"
"\tmask %s;\n",
mask);
} else
{
if (password_type)
{
snprintf(buf, sizeof(buf),
"webirc {\n"
"\tmask %s;\n"
"\tpassword \"%s\" { %s; };\n"
"};\n",
mask,
password,
password_type);
} else
{
snprintf(buf, sizeof(buf),
"webirc {\n"
"\tmask %s;\n"
"\tpassword \"%s\";\n"
"};\n",
mask,
password);
}
}
replace_section(ce, buf);
config_status("- cgiirc block upgraded and renamed to webirc");
return 1;
}
int contains_flag(char **flags, int flagscnt, char *needle)
{
int i;
for (i = 0; i < flagscnt; i++)
if (!strcmp(flags[i], needle))
return 1;
return 0;
}
int upgrade_oper_block(ConfigEntry *ce)
{
ConfigEntry *cep, *cepp;
char *name = NULL;
char *password = NULL;
char *password_type = NULL;
char *require_modes = NULL;
char *class = NULL;
char *flags[MAXOPTIONS];
int flagscnt = 0;
char *swhois = NULL;
char *snomask = NULL;
char *modes = NULL;
int maxlogins = -1;
char *fromlist[MAXFROMENTRIES];
int fromlistcnt = 0;
char maskbuf[1024];
char *operclass = NULL; /* set by us, not read from conf */
char *vhost = NULL; /* set by us, not read from conf */
int i;
char silly[64];
memset(flags, 0, sizeof(flags));
*maskbuf = '\0';
name = ce->ce_vardata;
if (!name)
return 0; /* oper block without a name = invalid */
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!strcmp(cep->ce_varname, "operclass"))
return 0; /* already 4.x conf */
else if (!strcmp(cep->ce_varname, "flags"))
{
if (cep->ce_vardata) /* short options (flag letters) */
{
char *p;
for (p = cep->ce_vardata; *p; p++)
{
if (flagscnt == MAXOPTIONS)
break;
for (i = 0; FlagMappingTable[i].shortflag; i++)
{
if (FlagMappingTable[i].shortflag == *p)
{
flags[flagscnt] = FlagMappingTable[i].longflag;
flagscnt++;
break;
}
}
}
}
else if (cep->ce_entries) /* long options (flags written out) */
{
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
{
if (flagscnt == MAXOPTIONS)
break;
flags[flagscnt++] = cepp->ce_varname;
}
}
}
else if (!strcmp(cep->ce_varname, "from"))
{
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
{
if (!strcmp(cepp->ce_varname, "userhost") && cepp->ce_vardata)
{
if (fromlistcnt == MAXFROMENTRIES)
break; // no room, sorry.
fromlist[fromlistcnt++] = cepp->ce_vardata;
}
}
}
else if (!strcmp(cep->ce_varname, "mask"))
{
/* processing mask here means we can also upgrade 3.4-alphaX oper blocks.. */
if (cep->ce_vardata)
{
if (fromlistcnt == MAXFROMENTRIES)
break; // no room, sorry.
fromlist[fromlistcnt++] = cep->ce_vardata;
} else
{
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
{
if (fromlistcnt == MAXFROMENTRIES)
break; // no room, sorry.
fromlist[fromlistcnt++] = cepp->ce_varname;
}
}
}
else if (!cep->ce_vardata)
continue; /* invalid */
else if (!strcmp(cep->ce_varname, "password"))
{
password = cep->ce_vardata;
if (cep->ce_entries)
password_type = cep->ce_entries->ce_varname;
}
else if (!strcmp(cep->ce_varname, "require-modes"))
{
require_modes = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "class"))
{
class = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "swhois"))
{
swhois = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "snomasks"))
{
snomask = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "modes"))
{
modes = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "maxlogins"))
{
maxlogins = atoi(cep->ce_vardata);
}
}
if ((fromlistcnt == 0) || !password || !class)
return 0; /* missing 3.2.x items in allow block (invalid or upgraded already) */
/* build oper::mask list in 'maskbuf' (includes variable name) */
if (fromlistcnt == 1)
{
char *m = !strncmp(fromlist[0], "*@", 2) ? fromlist[0]+2 : fromlist[0]; /* skip or don't skip the user@ part */
snprintf(maskbuf, sizeof(maskbuf), "mask %s;\n", m);
} else
{
/* Multiple (list of masks) */
int i;
snprintf(maskbuf, sizeof(maskbuf), "mask {\n");
for (i=0; i < fromlistcnt; i++)
{
char *m = !strncmp(fromlist[i], "*@", 2) ? fromlist[i]+2 : fromlist[i]; /* skip or don't skip the user@ part */
snprintf(maskbuf+strlen(maskbuf), sizeof(maskbuf)-strlen(maskbuf), "\t%s;\n", m);
}
strlcat(maskbuf, "\t};\n", sizeof(maskbuf));
}
/* Figure out which default operclass looks most suitable (="find highest rank") */
if (contains_flag(flags, flagscnt, "netadmin"))
operclass = "netadmin";
else if (contains_flag(flags, flagscnt, "services-admin"))
operclass = "services-admin";
else if (contains_flag(flags, flagscnt, "coadmin"))
operclass = "coadmin";
else if (contains_flag(flags, flagscnt, "admin"))
operclass = "admin";
else if (contains_flag(flags, flagscnt, "global"))
operclass = "globop";
else if (contains_flag(flags, flagscnt, "local"))
operclass = "locop";
else
{
/* Hmm :) */
config_status("WARNING: I have trouble converting oper block '%s' to the new system. "
"I made it use the locop operclass. Feel free to change", name);
operclass = "locop";
}
if (contains_flag(flags, flagscnt, "get_host") || upgrade.host_on_oper_up)
{
if (!strcmp(operclass, "netadmin"))
vhost = upgrade.netadmin_host;
else if (!strcmp(operclass, "services-admin"))
vhost = upgrade.sadmin_host;
else if (!strcmp(operclass, "coadmin"))
vhost = upgrade.coadmin_host;
else if (!strcmp(operclass, "admin"))
vhost = upgrade.admin_host;
else if (!strcmp(operclass, "globop"))
vhost = upgrade.oper_host;
else if (!strcmp(operclass, "locop"))
vhost = upgrade.locop_host;
}
/* If no swhois is set, then set a title. Just because people are used to it. */
if (!swhois)
{
if (!strcmp(operclass, "netadmin"))
swhois = "is a Network Administrator";
else if (!strcmp(operclass, "services-admin"))
swhois = "is a Services Administrator";
else if (!strcmp(operclass, "coadmin"))
swhois = "is a Co Administrator";
else if (!strcmp(operclass, "admin"))
swhois = "is a Server Administrator";
}
/* The 'coadmin' operclass is actually 'admin'. There's no difference in privileges. */
if (!strcmp(operclass, "coadmin"))
operclass = "admin";
/* convert globop and above w/override to operclassname-with-override */
if (contains_flag(flags, flagscnt, "can_override") && strcmp(operclass, "locop"))
{
snprintf(silly, sizeof(silly), "%s-with-override", operclass);
operclass = silly;
}
/* Ok, we got everything we need. Now we will write out the actual new oper block! */
/* oper block header & oper::mask */
snprintf(buf, sizeof(buf), "oper %s {\n"
"\t%s",
name,
maskbuf);
/* oper::password */
if (password_type)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tpassword \"%s\" { %s; };\n", password, password_type);
else
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tpassword \"%s\";\n", password);
/* oper::require-modes */
if (require_modes)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\trequire-modes \"%s\";\n", require_modes);
/* oper::maxlogins */
if (maxlogins != -1)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tmaxlogins %d;\n", maxlogins);
/* oper::class */
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tclass %s;\n", class);
/* oper::operclass */
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\toperclass %s;\n", operclass);
/* oper::modes */
if (modes)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tmodes \"%s\";\n", modes);
/* oper::snomask */
if (snomask)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tsnomask \"%s\";\n", snomask);
/* oper::vhost */
if (vhost)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tvhost \"%s\";\n", vhost);
/* oper::swhois */
if (swhois)
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tswhois \"%s\";\n", swhois);
strlcat(buf, "};\n", sizeof(buf));
replace_section(ce, buf);
config_status("- oper block (%s) converted to new syntax", name);
return 1;
}
void update_read_settings(char *cfgfile)
{
ConfigFile *cf = NULL;
ConfigEntry *ce = NULL, *cep, *cepp;
cf = config_load(cfgfile, NULL);
if (!cf)
return;
if (strstr(cfgfile, "modules.default.conf"))
needs_modules_default_conf = 0;
else if (strstr(cfgfile, "operclass.default.conf"))
needs_operclass_default_conf = 0;
/* This needs to be read early, as the rest may depend on it */
for (ce = cf->cf_entries; ce; ce = ce->ce_next)
{
if (!strcmp(ce->ce_varname, "set"))
{
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!strcmp(cep->ce_varname, "hosts"))
{
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
{
if (!cepp->ce_vardata)
continue;
if (!strcmp(cepp->ce_varname, "local")) {
safe_strdup(upgrade.locop_host, cepp->ce_vardata);
}
else if (!strcmp(cepp->ce_varname, "global")) {
safe_strdup(upgrade.oper_host, cepp->ce_vardata);
}
else if (!strcmp(cepp->ce_varname, "coadmin")) {
safe_strdup(upgrade.coadmin_host, cepp->ce_vardata);
}
else if (!strcmp(cepp->ce_varname, "admin")) {
safe_strdup(upgrade.admin_host, cepp->ce_vardata);
}
else if (!strcmp(cepp->ce_varname, "servicesadmin")) {
safe_strdup(upgrade.sadmin_host, cepp->ce_vardata);
}
else if (!strcmp(cepp->ce_varname, "netadmin")) {
safe_strdup(upgrade.netadmin_host, cepp->ce_vardata);
}
else if (!strcmp(cepp->ce_varname, "host-on-oper-up")) {
upgrade.host_on_oper_up = config_checkval(cepp->ce_vardata,CFG_YESNO);
}
}
}
}
}
}
config_free(cf);
}
int update_conf_file(void)
{
ConfigFile *cf = NULL;
ConfigEntry *ce = NULL, *cep, *cepp;
int update_conf_runs = 0;
again:
if (update_conf_runs++ > 100)
{
config_error("update conf re-run overflow. whoops! upgrade failed! sorry!");
return 0;
}
if (cf)
{
config_free(cf);
cf = NULL;
}
cf = config_load(configfiletmp, NULL);
if (!cf)
{
config_error("could not load configuration file '%s'", configfile);
return 0;
}
for (ce = cf->cf_entries; ce; ce = ce->ce_next)
{
/*printf("%s%s%s\n",
ce->ce_varname,
ce->ce_vardata ? " " : "",
ce->ce_vardata ? ce->ce_vardata : ""); */
if (!strcmp(ce->ce_varname, "loadmodule"))
{
if (upgrade_loadmodule(ce))
goto again;
}
if (!strcmp(ce->ce_varname, "include"))
{
if (upgrade_include(ce))
goto again;
}
if (!strcmp(ce->ce_varname, "me"))
{
if (upgrade_me_block(ce))
goto again;
}
if (!strcmp(ce->ce_varname, "link"))
{
if (upgrade_link_block(ce))
goto again;
}
if (!strcmp(ce->ce_varname, "oper"))
{
if (upgrade_oper_block(ce))
goto again;
}
if (!strcmp(ce->ce_varname, "vhost"))
{
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!strcmp(cep->ce_varname, "from"))
{
if (upgrade_from_subblock(cep))
goto again;
}
}
}
if (!strcmp(ce->ce_varname, "spamfilter"))
{
if (upgrade_spamfilter_block(ce))
goto again;
}
if (!strcmp(ce->ce_varname, "allow") && !ce->ce_vardata) /* 'allow' block for clients, not 'allow channel' etc.. */
{
if (upgrade_allow_block(ce))
goto again;
}
if (!strcmp(ce->ce_varname, "listen"))
{
if (upgrade_listen_block(ce))
goto again;
}
if (!strcmp(ce->ce_varname, "cgiirc"))
{
if (upgrade_cgiirc_block(ce))
goto again;
}
if (!strcmp(ce->ce_varname, "set"))
{
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!strcmp(cep->ce_varname, "throttle"))
{
int n = 0, t = 0;
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
{
if (!cepp->ce_vardata)
continue;
if (!strcmp(cepp->ce_varname, "period"))
t = config_checkval(cepp->ce_vardata, CFG_TIME);
else if (!strcmp(cepp->ce_varname, "connections"))
n = atoi(cepp->ce_vardata);
}
remove_section(cep->ce_fileposstart, cep->ce_fileposend);
snprintf(buf, sizeof(buf), "anti-flood { connect-flood %d:%d; };\n",
n, t);
insert_section(cep->ce_fileposstart, buf);
goto again;
} else
if (!strcmp(cep->ce_varname, "hosts"))
{
config_status("- removed set::hosts. we now use oper::vhost for this.");
remove_section(cep->ce_fileposstart, cep->ce_fileposend); /* hmm something is wrong here */
goto again;
} else
if (!strcmp(cep->ce_varname, "dns"))
{
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
if (!strcmp(cepp->ce_varname, "nameserver") ||
!strcmp(cepp->ce_varname, "timeout") ||
!strcmp(cepp->ce_varname, "retries"))
{
config_status("- removed set::dns::%s. this option is never used.", cepp->ce_varname);
remove_section(cepp->ce_fileposstart, cepp->ce_fileposend);
goto again;
}
}
}
}
}
if (cf)
config_free(cf);
return (update_conf_runs > 1) ? 1 : 0;
}
static int already_included(char *fname, ConfigFile *cf)
{
for (; cf; cf = cf->cf_next)
if (!strcmp(cf->cf_filename, fname))
return 1;
return 0;
}
static void add_include_list(char *fname, ConfigFile **cf)
{
ConfigFile *n = safe_alloc(sizeof(ConfigFile));
// config_status("INCLUDE: %s", fname);
safe_strdup(n->cf_filename, fname);
n->cf_next = *cf;
*cf = n;
}
void build_include_list_ex(char *fname, ConfigFile **cf_list)
{
ConfigFile *cf;
ConfigEntry *ce;
if (strstr(fname, "://"))
return; /* Remote include - ignored */
add_include_list(fname, cf_list);
cf = config_load(fname, NULL);
if (!cf)
return;
for (ce = cf->cf_entries; ce; ce = ce->ce_next)
if (!strcmp(ce->ce_varname, "include"))
{
if ((ce->ce_vardata[0] != '/') && (ce->ce_vardata[0] != '\\') && strcmp(ce->ce_vardata, CPATH))
{
char *str = safe_alloc(strlen(ce->ce_vardata) + strlen(CONFDIR) + 4);
sprintf(str, "%s/%s", CONFDIR, ce->ce_vardata);
safe_free(ce->ce_vardata);
ce->ce_vardata = str;
}
if (!already_included(ce->ce_vardata, *cf_list))
build_include_list_ex(ce->ce_vardata, cf_list);
}
config_free(cf);
}
ConfigFile *build_include_list(char *fname)
{
ConfigFile *cf_list = NULL;
build_include_list_ex(fname, &cf_list);
return cf_list;
}
void update_conf(void)
{
ConfigFile *files;
ConfigFile *cf;
char *mainconf = configfile;
int upgraded_files = 0;
char answerbuf[128], *answer;
config_status("You have requested to upgrade your configuration files.");
config_status("If you are upgrading from 4.x to 5.x then DO NOT run this script. This script does NOT update config files from 4.x -> 5.x.");
config_status("UnrealIRCd 4.2.x configuration files should work OK on 5.x, with only some warnings printed when you boot the IRCd.");
config_status("See https://www.unrealircd.org/docs/Upgrading_from_4.x#Configuration_changes");
config_status("This upgrade-conf script is only useful if you are upgrading from 3.2.x.");
config_status("");
#ifndef _WIN32
do
{
printf("Continue upgrading 3.2.x to 4.x configuration file format? (Y/N): ");
*answerbuf = '\0';
answer = fgets(answerbuf, sizeof(answerbuf), stdin);
if (answer && (toupper(*answer) == 'N'))
{
printf("Configuration unchanged.\n");
return;
}
if (answer && (toupper(*answer) == 'Y'))
{
break;
}
printf("Invalid response. Please enter either Y or N\n\n");
} while(1);
#endif
strlcpy(me.name, "<server>", sizeof(me.name));
memset(&upgrade, 0, sizeof(upgrade));
files = build_include_list(mainconf);
/* We need to read some original settings first, before we touch anything... */
for (cf = files; cf; cf = cf->cf_next)
{
update_read_settings(cf->cf_filename);
}
/* Now go upgrade... */
for (cf = files; cf; cf = cf->cf_next)
{
if (!file_exists(cf->cf_filename))
continue; /* skip silently. errors were already shown earlier by build_include_list anyway. */
configfile = cf->cf_filename;
config_status("Checking '%s'...", cf->cf_filename);
snprintf(configfiletmp, sizeof(configfiletmp), "%s.tmp", configfile);
unlink(configfiletmp);
if (!unreal_copyfileex(configfile, configfiletmp, 0))
{
config_error("Could not create temp file for processing!");
die();
}
if (update_conf_file())
{
char buf[512];
snprintf(buf, sizeof(buf), "%s.old", configfile);
if (file_exists(buf))
{
int i;
for (i=0; i<100; i++)
{
snprintf(buf, sizeof(buf), "%s.old.%d", configfile, i);
if (!file_exists(buf))
break;
}
}
/* rename original config file to ... */
if (rename(configfile, buf) < 0)
{
config_error("Could not rename original conf '%s' to '%s'", configfile, buf);
die();
}
/* Rename converted conf to config file */
#ifdef _WIN32
/* "If newpath already exists it will be atomically replaced"..
* well.. not on Windows! Error: "File exists"...
*/
unlink(configfile);
#endif
if (rename(configfiletmp, configfile) < 0)
{
config_error("Could not rename converted configuration file '%s' to '%s' -- please rename this file yourself!",
configfiletmp, configfile);
die();
}
config_status("File '%s' upgrade complete.", configfile);
upgraded_files++;
} else {
unlink(configfiletmp);
config_status("File '%s' left unchanged (no upgrade necessary)", configfile);
}
}
configfile = mainconf; /* restore */
if (needs_operclass_default_conf)
{
/* There's a slight chance we never added this include, and you get mysterious
* oper permissions errors if you try to use such an operclass and it's missing.
*/
FILE *fd = fopen(mainconf, "a");
if (fd)
{
fprintf(fd, "\ninclude \"operclass.default.conf\";\n");
fclose(fd);
config_status("Oh wait, %s needs an include for operclass.default.conf. Added.", mainconf);
upgraded_files++;
}
}
if (upgraded_files > 0)
{
config_status("");
config_status("%d configuration file(s) upgraded. You can now boot UnrealIRCd with your freshly converted conf's!", upgraded_files);
config_status("You should probably take a look at the converted configuration files now or at a later time.");
config_status("See also https://www.unrealircd.org/docs/Upgrading_from_3.2.x and the sections in there (eg: Oper block)");
config_status("");
} else {
config_status("");
config_status("No configuration files were changed. No upgrade was needed. If this is incorrect then please report on https://bugs.unrealircd.org/ !");
config_status("");
}
}