From: Remco Rijnders Date: Sat, 7 Mar 2026 17:17:36 +0000 (-0500) Subject: Switch ircd.conf to block-based configuration format X-Git-Url: http://git.serene-ircd.net/?a=commitdiff_plain;h=e6b6b8d0f3995cd247db39c42882ae5382865fe9;p=serene-ircd.git Switch ircd.conf to block-based configuration format Replace the legacy colon-delimited config format with a block-based format using curly braces and key-value pairs. This eliminates the colon separator conflict with IPv6 addresses and supports multi-value fields (e.g. multiple hosts in a single kline block). The connect{} block merges the old C/N/H line triplet into a single block. The allow{} block replaces I-lines with separate ip and host keys. All other block types map directly to their legacy equivalents. Includes a one-time converter script (tools/convert-conf.py) to migrate existing configs, and a rewritten chkconf validator. Co-Authored-By: Claude Opus 4.6 --- diff --git a/doc/example.conf b/doc/example.conf index 356e133..358e0cc 100644 --- a/doc/example.conf +++ b/doc/example.conf @@ -16,323 +16,303 @@ # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # -# Example ircd.conf file for Serene5.28 or newer. +# Example ircd.conf file for Serene IRCD. # # -# In this file I will explain what the different lines in the ircd.conf file -# do. You may want to test config changes with chkconf before you try them -# on your server to prevent disruption to your users due to errors. +# This file uses a block-based configuration format. Each configuration +# directive is a named block with key-value pairs inside curly braces. +# Lines are terminated with semicolons. Comments begin with #. # -# In this file I use the term "ircd" to designate an irc server (daemon). -# -# The following terms can be used to determine if you need a line or not: -# MANDATORY: The ircd requires the line to operate. -# NETWORKED: The ircd needs the line when it is linked to one or more other -# servers +# The following terms can be used to determine if you need a block or not: +# MANDATORY: The ircd requires the block to operate. +# NETWORKED: The ircd needs the block when it is linked to other servers. # SUGGESTED: It would most likely be a good idea if you used this. # OPTIONAL: It's up to the person doing the configuration. # DISCOURAGED: Probably not a good idea, except in limited circumstances. -# OBSOLETE: Old, out of date, don't use this # -# NOTE: Many networks will require certain configuration lines to allow -# their networks to run more smoothly. If you are given lines by -# the network you are linking to then they should be implemented +# NOTE: Many networks will require certain configuration to allow +# their networks to run more smoothly. If you are given config +# by the network you are linking to then they should be implemented # regardless of this file's recommendation. # -# This file goes through the lines in the order they should be placed in -# a functional ircd.conf file. -# -# NOTE: Keep in mind that this file is read in REVERSE, that means the -# bottom line is the first one read and the top line is the last. -# Remember this when you are doing anything that needs to be in -# a specific order such as I-lines. -# -# -# -# M: MANDATORY. This line specifies the basic information the server needs -# about itself to operate. -# -# M:server name:IP address:Server description:port -# Server name is the name the server shows to clients and other servers on -# the network. -# IP address tells the server what address on the machine to bind to, if -# you fill the field with a "*", or leave it empty, it will bind itself to -# all addresses it finds on the machine. -# Server description is the description of the server shown to clients and -# other servers on the network. -# Port is the default port the server will attach itself to. -# -# This line names the server Server1.Serenity-IRC.Net and attaches itself to -# all addresses attached to the machine on port 7000: -# -M:Server1.Serenity-IRC.Net:*:Cool new server:7000 -# -# -# -# A: MANDATORY. This is your administration info that is shown when someone -# on your server types "/admin" This is a good place to keep the information -# users need to contact someone in charge. -# -# ":" Designates a new line, other than that it's pretty much free form. -# -# Here is a format often used: -# A:server's provider:admin: admin's email : coadmin: coadmin's email -# -A:Lame1 colocations:admin John Random: email JRandom@lame1.com -# -# -# -# Y: SUGGESTED. Defines connection class types and behavior. -# Class is the number representing the Y line, Ping frequency defines the -# duration between pings. Auto-connect frequency defines how often the server -# will try to connect to the server defined with the corresponding connection -# class: max connection defines the maximum number of connections the server -# will allow using this connection class. -# -# Sendq is the maximum space the server will use to buffer outgoing -# messages. If the sendq is filled, the affected connection is dropped. -# I reccomend setting this low for clients (prevents flooding) and high -# for servers. -# -# NOTE: The max connections for all Y-lines should add up to the total -# number of connections the the server can handle MINUS the number of -# file desrcriptors that are needed for things like wingate checking and one -# for the config file. ( 5-10 should do) -# -# For example: A server compiled for 256 max filedescriptors should have all -# Y-lines add up to 250 (remove 6 for server internal use) -# -# -# Y:class:ping frequency:auto-connect frequency:max connections:max sendq -# -# This is a typical client Y-line no auto-connect, max 20 connections. -Y:1:60:0:20:100000 -# -# This is an example of a Y-line that could be used on your primary hub -# -Y:40:120:60:1:3000000 -# This is an example of a Y-line that could be used for your secondary hubs -Y:50:120:0:5:3000000 -# -# -# -# I: MANDATORY. These lines define who can connect to a server, without -# them no one is able to connect to the server. -# -# I:IP mask:password:domain mask::Y-Line (optional) -# -# IP Mask is the numeric ip mask (ex 192.168.*) if Arbitrary text is -# entered such as NOMATCH the I-line will only match based on the domain -# name and will disallow clients from addresses that lack reverse lookup -# The password is optional, and is needed to connect using that I line. -# -# The domain name is the name returned from a reverse DNS lookup, the Y-line -# of course is the Y-line that you want to correspond to the connection. -# NOTE: I-lines are read starting from the bottom passing though each one -# until a line is found that matches the client's connection if none -# is found access to the server is denied. -# -# This is a I-line that will allow anyone at all to connect. -I:*@*::*@*::1 -# -# -# -# P: SUGGESTED. Allows you to add ports and addresses/domain sockets other -# than the one specified in the M-line. -# -# P:address:*:*:port -# -# To have the server bind all addresses available on a given port leave the -# leave the field blank. -# NOTE: if a given port is already in use the server will simply fail to -# load. -# -# This line would have the server look for connections to port 7000 on -# the address 127.0.0.1 -# -P:127.0.0.1:*:*:7000 -# -# -# -# O: SUGGESTED. O-lines define who has Oper access on the server. How -# necessary these line are depend on how well your site is configured and the -# philosophy of the network you are linked to. -# -# O:address:password:nick:flags:Y-line -# -# If the server is compiled to use encrypted Oper passwords then you must -# use mkpasswd to convert the password from plaintext. -# -# NOTE: Addresses using the numeric IP address will work even if it -# resolves. +# NOTE: The config file is read top-to-bottom. For directives where order +# matters (such as allow blocks), the first matching block wins. +# +# +# +# me {} - MANDATORY +# Specifies the basic information the server needs about itself. +# +# name - The server name shown to clients and other servers. +# info - The server description shown to clients and other servers. +# address - IP address to bind to. Use "*" or omit to bind all addresses. +# port - Default port the server will listen on. +# +me { + name Server1.Serenity-IRC.Net; + info "Cool new server"; + address *; + port 7000; +}; + +# +# admin {} - MANDATORY +# Administration info shown when someone types /admin. +# Each "line" directive adds a line of output. +# +admin { + line "Lame1 colocations"; + line "admin John Random"; + line "email JRandom@lame1.com"; +}; + +# +# class {} - SUGGESTED +# Defines connection class types and behavior. +# +# name - Class number (referenced by other blocks). +# pingfreq - Duration between pings in seconds. +# connfreq - How often to auto-connect (0 = never). +# maxlinks - Maximum number of connections in this class. +# sendq - Maximum send queue size in bytes. +# +# NOTE: The maxlinks for all classes should add up to the total number of +# connections the server can handle minus a few for internal use. +# +# Typical client class: no auto-connect, max 20 connections. +class { + name 1; + pingfreq 60; + connfreq 0; + maxlinks 20; + sendq 100000; +}; + +# Primary hub class. +class { + name 40; + pingfreq 120; + connfreq 60; + maxlinks 1; + sendq 3000000; +}; + +# Secondary hub class. +class { + name 50; + pingfreq 120; + connfreq 0; + maxlinks 5; + sendq 3000000; +}; + +# +# allow {} - MANDATORY (replaces I-lines) +# Defines who can connect to this server. Without allow blocks, no one +# can connect. The first matching block is used. +# +# ip - Numeric IP mask (e.g. 192.168.* or 2001:db8::*). +# Use "NOMATCH" to only match by hostname. +# host - Hostname mask from reverse DNS lookup. +# password - Optional password required to connect. +# class - Connection class number. +# port - Optional: only match connections on this port. +# +# This block allows anyone to connect. +allow { + ip *@*; + host *@*; + class 1; +}; + +# +# listen {} - SUGGESTED (replaces P-lines) +# Adds additional listen ports beyond the one in the me {} block. +# +# address - IP address to bind (omit or "*" for all addresses). +# port - Port number to listen on. +# +listen { + address 127.0.0.1; + port 7000; +}; + +# Listen on an IPv6 address: +#listen { +# address "::1"; +# port 7000; +#}; + +# +# oper {} - SUGGESTED (replaces O-lines) +# Defines who has Oper access on the server. +# +# host - Address mask the oper must connect from. +# password - Oper password (use mkpasswd if compiled with encryption). +# name - Oper nickname. +# flags - Access flags (see below). +# class - Connection class number. # # flags: -# a = can set +a Designates a services oper -# A = can set +A to set server admin -# b = can set temp klines using /kline -# B = can remove temp klines using /unkline -# c = can use /squit and /connect on locally connected servers -# C = can use /squit and /connect on any server -# D = can shut down the server using /die -# f = can see server flood kills by setting /umode +f -# g = can send globop messages -# h = can see helpop requests by setting /umode +h -# k = can kill clients connected locally -# K = can kill any clients on the network -# l = can send locop messages -# n = can send local server messages (/msg $servername message to users) -# N = can send global server messages (/msg $*.net message to users) -# o = designates a local oper includes flags: bBcfghklnruw -# O = designates a global oper includes flags: CKNo -# r = can /rehash the server -# R = can /restart the server -# u = can see local connects and disconnects using /umode +c -# w = can send wallops (obsolete) +# a = can set +a (services oper) A = can set +A (server admin) +# b = can set temp klines B = can remove temp klines +# c = local /squit and /connect C = remote /squit and /connect +# D = can /die the server f = can see flood kills (+f) +# g = can send globops h = can see helpop requests (+h) +# k = can kill local clients K = can kill any client +# l = can send locops n = local server messages +# N = global server messages o = local oper (includes bBcfghklnruw) +# O = global oper (includes CKNo) r = can /rehash +# R = can /restart u = can see connects (+c) +# w = can send wallops # # NOTE: Access to everything is: AaDOR # -O:192.168.2.*:notarealpassword:AnOper:OR:10 -# -# -# -# X: SUGGESTED. Sets a password for the /die and /restart commands. -# -# X:Die password:Restart password -# -X:killme:restartme -# -# -# -# C: NETWORKED. Connect lines, this line defines who your server can -# connect to. C-lines must be paired with an N line to work. -# -# C:remote address:password:remote server name:port to auto-connect:Y-line -# The address is the machine's Internet address, the server name is -# the server's name on irc network as defined by the machine's M-line. -# They do not have to be the same. -# NOTE: Using the numeric ip address for the machine's address is slightly -# faster and more secure than using its domain name. -# -C:192.168.2.1:notarealpassword:HubServer.Serenity-IRC.Net:7827:45 -# -# -# -# N: NETWORKED. This line defines what servers your server can connect -# to yours. N-lines must be paired with an C line to work correctly. -# -# N:remote address:password:remote server name::Y-line -# The address is the machine's address on the Internet, the server name is -# the server's name on irc network as defined by the machine's M-line. -# They do not have to be the same. -# -# NOTE: Using the numeric IP address for the machine's address is slightly -# faster and more secure than using its domain name. -# -N:192.168.2.1:notarealpassword:HubServer.Serenity-IRC.Net::40 -# -# -# -# U: NETWORKED. Determines what servers may make changes to things like -# channel modes without the server sounding alarms or attempting to stop -# them. -# -# U:Server-Name:*:* -# -# This line allows services to have the access needed to do its job. -# -U:Services.Serenity-IRC.Net:*:* -# -# -# -# H: NETWORKED. Hub lines. These define what servers a given hub is permitted -# to connect to the network. -# H:servers the hub is allowed connect::name -# -# NOTE: The name is the name defined in the hub's M-line it might not be the -# same as its address. -# -# This would allow HubServer.Serenity-IRC.Net to hub any servers while being directly -# connected to your server. -H:*::HubServer.Serenity-IRC.Net -# -# This line would only allow HubServer2 to have Mexican servers as leaves. -H:*.mx.*:HubServer2.Serenity-IRC.Net -# -# -# -# G: OPTIONAL. General configuration options. These control runtime behavior -# that was previously set at compile time. -# -# G:option -# -# Currently supported options: -# hub - Enable hub mode. A hub server accepts multiple server connections -# and routes traffic between them. Without this option, the server -# runs as a leaf and will only maintain a single server link. -# This setting is re-evaluated on /rehash. -# -# Uncomment the following line if this server is a hub: -#G:hub -# -# -# -# Q: OPTIONAL. Nick quarantine. This line prevents non opers from using -# nicks covered by the Q-line. -# -# Q::reason:nick -# -Q::users may not use services nicks:*serv* -# -# -# -# q: OBSOLETE. Server quarantine. This line quarantines the specified -# server, the line must be on ALL SERVERS connected to the network or net -# spits will occur. -# -# q::reason:server -# -q::I have no idea why:unknown.Serenity-IRC.Net -# -# -# -# K: OPTIONAL. Kill Line. K-lines are a means of denying access to -# certain clients. -# -# K-lines work best for addresses that are likely to be banned long term -# and therefore a temporary kline or akill won't do. -# -# K:address:reason:ident -# -# Address is the address or mask of the client you wish to ban. -# NOTE: If you ban an IP address and reverse DNS is successful the -# domain name returned will be checked and NOT the IP address, any bans -# on the IP address will be IGNORED. This applies to both Klines -# and A-kills. -# -# The reason field is what is shown to the client on the receiving end -# of the kline. -# -# The ident is checked against what is returned by the users, keep in mind -# that this is easily changeable, and not useful if you're trying to keep a -# particular user off the network, although it is a lot more logical for temp -# K-lines or A-kills since they are more readily changed. -# This will ban any user from lame.com and tell them it was for mass -# advertising: -K:*.lame.com:Mass advertising:* -# -# This will ban anyone using the ident Sphere and tell them to get -# a new script. -K:*:Get a new script:Sphere -# -# -# Z: DISCOURAGED. Disconnects the client at the earliest stage of connection. -# useful for preventing annoying server messages by persistent attempted -# server connections, or getting rid of people ban evading using vhosts. -# -# NOTE: 1 The address MUST be a numeric IP or it won't work. -# NOTE: 2 The last field must be a * or strange things may happen to the -# server. -# -# Z:address:reason:* -# -Z:192.168.2.2:not a server:* +oper { + host 192.168.2.*; + password notarealpassword; + name AnOper; + flags OR; + class 10; +}; + +# +# drpass {} - SUGGESTED (replaces X-lines) +# Sets passwords for the /die and /restart commands. +# +drpass { + die killme; + restart restartme; +}; + +# +# connect {} - NETWORKED (replaces C/N-line pairs) +# Defines a server this server can link with. Each connect block +# combines the old C-line (outgoing) and N-line (incoming) into one. +# +# host - Remote server's IP address or hostname. +# password - Link password (must match on both ends). +# name - Remote server's IRC name (from their me {} block). +# port - Port to auto-connect on (0 = no auto-connect). +# class - Connection class number. +# hub - Optional: mask of servers this link may introduce. +# Use "*" to allow any. Omit for leaf-only links. +# +connect { + host 192.168.2.1; + password notarealpassword; + name HubServer.Serenity-IRC.Net; + port 7827; + class 40; + hub *; +}; + +# A connect block where the remote is only allowed to hub Mexican servers: +#connect { +# host 192.168.2.2; +# password anotherpassword; +# name HubServer2.Serenity-IRC.Net; +# port 0; +# class 50; +# hub *.mx.*; +#}; + +# +# uworld {} - NETWORKED (replaces U-lines) +# Determines which servers may make privileged changes (e.g. services). +# +uworld { + name Services.Serenity-IRC.Net; +}; + +# +# general {} - OPTIONAL (replaces G-lines) +# General runtime configuration options. +# +# hub - Enable hub mode. A hub accepts multiple server connections +# and routes traffic between them. Without this, the server +# runs as a leaf with a single server link. +# Re-evaluated on /rehash. +# +# Uncomment the following to enable hub mode: +#general { +# hub yes; +#}; + +# +# quarantine {} - OPTIONAL (replaces Q-lines for nicks) +# Prevents non-opers from using matching nicknames. +# +# nick - Nick mask to quarantine (can use wildcards). +# reason - Reason shown to users. +# +quarantine { + nick *serv*; + reason "users may not use services nicks"; +}; + +# +# squar {} - server quarantine (replaces q-lines) +# Quarantines a server. Must be on ALL servers or netsplits will occur. +# +# name - Server name to quarantine. +# reason - Reason for quarantine. +# +#squar { +# name unknown.Serenity-IRC.Net; +# reason "I have no idea why"; +#}; + +# +# kline {} - OPTIONAL (replaces K-lines) +# Denies access to matching clients. Multiple host entries can share +# a single reason, avoiding repetition. +# +# host - Address or mask to ban. Can be repeated for multiple hosts. +# ident - Ident/username mask (default: *). +# reason - Reason shown to the banned client. +# +# NOTE: If reverse DNS succeeds, the hostname is checked, not the IP. +# To ban by IP when DNS resolves, add both hostname and IP bans. +# +# Ban all users from lame.com: +kline { + host *.lame.com; + ident *; + reason "Mass advertising"; +}; + +# Ban a specific ident from anywhere: +kline { + host *; + ident Sphere; + reason "Get a new script"; +}; + +# Ban multiple hosts for the same reason (replaces many old K-lines): +#kline { +# host *.spam1.example.com; +# host *.spam2.example.com; +# host *.spam3.example.com; +# ident *; +# reason "Spam network"; +#}; + +# +# zline {} - DISCOURAGED (replaces Z-lines) +# Disconnects clients at the earliest stage of connection by IP. +# Useful for persistent connection attempts or ban evasion via vhosts. +# +# address - Numeric IP address (must be numeric, not hostname). +# reason - Reason for the ban. +# +zline { + address 192.168.2.2; + reason "not a server"; +}; + +# IPv6 zline example: +#zline { +# address "2001:db8::dead:beef"; +# reason "not welcome"; +#}; diff --git a/include/h.h b/include/h.h index 3c04e96..04915a0 100644 --- a/include/h.h +++ b/include/h.h @@ -123,7 +123,6 @@ extern void RemoveZLine(char *); extern char *MyMalloc (), *MyRealloc () ; extern char *debugmode, *configfile, *sbrk0; -extern char *getfield (char *); extern void get_sockhost (aClient *, char *); extern char *rpl_str (int), *err_str (int); extern char *strerror (int); diff --git a/src/chkconf.c b/src/chkconf.c index 4b0a99a..1a59598 100644 --- a/src/chkconf.c +++ b/src/chkconf.c @@ -27,547 +27,373 @@ #undef free #define MyMalloc(x) malloc(x) -#define Reg -static void new_class (); -static char *getfield (), confchar (); -static int openconf (), validate (); -static aClass *get_class (); -static aConfItem *initconf (); - -static int numclasses = 0, *classarr = (int *) NULL, debugflag = 0; +static int debugflag = 0; static char *configfile = CONFIGFILE; -static char nullfield[] = ""; -static char maxsendq[12]; +static int errors = 0; +static int has_me = 0, has_admin = 0; +static int num_chk_classes = 0; +static int chk_classes[256]; -int main (int argc, char *argv[]) +static void note_class (int cn) { - new_class (0); + int i; + for (i = 0; i < num_chk_classes; i++) + if (chk_classes[i] == cn) + return; + if (num_chk_classes < 256) + chk_classes[num_chk_classes++] = cn; +} - if (chdir (DPATH)) { - perror ("chdir"); - exit (-1); - } - if (argc > 1 && !strncmp (argv[1], "-d", 2)) { - debugflag = 1; - if (argv[1][2]) - debugflag = atoi (argv[1] + 2); - argc--, argv++; +static int class_exists (int cn) +{ + int i; + for (i = 0; i < num_chk_classes; i++) + if (chk_classes[i] == cn) + return 1; + return 0; +} + +/* Read entire file into a malloc'd buffer */ +static char *read_file (const char *path) +{ + struct stat st; + char *buf; + int fd, n, total = 0; + + fd = open (path, O_RDONLY); + if (fd < 0) + return NULL; + if (fstat (fd, &st) < 0) { + close (fd); + return NULL; } - else if (argc > 1 && !strncmp (argv[1], "-h", 2)) { - printf ("chkconf [-d] [conf file]\n"); - printf ("-d = debug mode\n"); - printf ("conf file = path to ircd.conf\n\n"); - exit (0); + buf = (char *) malloc (st.st_size + 1); + while (total < st.st_size) { + n = read (fd, buf + total, st.st_size - total); + if (n <= 0) + break; + total += n; } - if (argc > 1) - configfile = argv[1]; - return validate (initconf ()); + buf[total] = '\0'; + close (fd); + return buf; } -/* - * openconf - * - * returns -1 on any error or else the fd opened from which to read the - * configuration file from. - */ -static int openconf () +static char *skip_ws (char *p) { - return open (configfile, O_RDONLY); + while (*p) { + if (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') + p++; + else if (*p == '#') { + while (*p && *p != '\n') + p++; + } else + break; + } + return p; } -static int oper_access[] = { - ~(OFLAG_ADMIN | OFLAG_SADMIN | OFLAG_ZLINE), '*', - OFLAG_LOCAL, 'o', - OFLAG_GLOBAL, 'O', - OFLAG_REHASH, 'r', - OFLAG_DIE, 'D', - OFLAG_RESTART, 'R', - OFLAG_GLOBOP, 'g', - OFLAG_LOCOP, 'l', - OFLAG_LROUTE, 'c', - OFLAG_GROUTE, 'C', - OFLAG_LKILL, 'k', - OFLAG_GKILL, 'K', - OFLAG_KLINE, 'b', - OFLAG_UNKLINE, 'B', - OFLAG_LNOTICE, 'n', - OFLAG_GNOTICE, 'N', - OFLAG_ADMIN, 'A', - OFLAG_SADMIN, 'a', - OFLAG_UMODEC, 'u', - OFLAG_UMODEF, 'f', - OFLAG_ZLINE, 'z', - 0, 0 -}; +static char *get_token (char *p, char *dst, int dstsize) +{ + int i = 0; + + p = skip_ws (p); + if (*p == '"') { + p++; + while (*p && *p != '"' && i < dstsize - 1) { + if (*p == '\\' && *(p + 1)) { + p++; + switch (*p) { + case 'n': dst[i++] = '\n'; break; + case 't': dst[i++] = '\t'; break; + case '\\': dst[i++] = '\\'; break; + case '"': dst[i++] = '"'; break; + default: dst[i++] = *p; break; + } + p++; + } else + dst[i++] = *p++; + } + if (*p == '"') + p++; + } else { + while (*p && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n' + && *p != ';' && *p != '{' && *p != '}' && i < dstsize - 1) + dst[i++] = *p++; + } + dst[i] = '\0'; + return p; +} -/* - ** initconf() - ** Read configuration file. - ** - ** returns -1, if file cannot be opened - ** 0, if file opened - */ -static aConfItem * initconf (int opt) +/* Count approximate line number at position p within buf */ +static int line_number (char *buf, char *p) { - int fd; - char line[512], *tmp, c[80], *s; - int ccount = 0, ncount = 0, dh, flags = 0; - int lineno; - aConfItem *aconf = NULL, *ctop = NULL; + int n = 1; + char *s; + for (s = buf; s < p; s++) + if (*s == '\n') + n++; + return n; +} - (void) fprintf (stderr, "\nOpening %s as ircd configuration file\n\n", - configfile); - if ((fd = openconf ()) == -1) { - return NULL; +static int validate_conf (const char *path) +{ + char *filebuf, *p, *block_start; + char blocktype[512], key[512], value[512]; + int has_connect_port; + + filebuf = read_file (path); + if (!filebuf) { + fprintf (stderr, "ERROR: Cannot open %s\n", path); + return -1; } - lineno = 0; - (void) dgets (-1, NULL, 0); /* make sure buffer is at empty pos */ - while ((dh = dgets (fd, line, sizeof (line) - 1)) > 0) { - /* The +2 here is a silly hack, but it does cause the correct line number - * to be reported when it actually reaches the end of file case. This is - * the only place lineno should be incremented, otherwise we're causing - * an off by one error in the line number for each error reported. -Studded - */ - printf ("%u:\tEnd of file\r", (lineno++ + 2)); - if (aconf) { - if (aconf->host) - (void) free (aconf->host); - if (aconf->passwd) - (void) free (aconf->passwd); - if (aconf->name) - (void) free (aconf->name); - } - else - aconf = (aConfItem *) malloc (sizeof (*aconf)); - aconf->host = (char *) NULL; - aconf->passwd = (char *) NULL; - aconf->name = (char *) NULL; - aconf->class = (aClass *) NULL; - if ((tmp = (char *) strchr (line, '\n'))) - *tmp = 0; - else - while (dgets (fd, c, sizeof (c) - 1)) - if ((tmp = (char *) strchr (c, '\n'))) { - *tmp = 0; - break; - } - /* - * Do quoting of characters and # detection. - */ - for (tmp = line; *tmp; tmp++) { - if (*tmp == '\\') { - switch (*(tmp + 1)) { - case 'n': - *tmp = '\n'; - break; - case 'r': - *tmp = '\r'; - break; - case 't': - *tmp = '\t'; - break; - case '0': - *tmp = '\0'; - break; - default: - *tmp = *(tmp + 1); - break; - } - if (!*(tmp + 1)) - break; - else - for (s = tmp; (*s = *++s);); - tmp++; - } - else if (*tmp == '#') - *tmp = '\0'; - } - if (!*line || *line == '#' || *line == '\n' || - *line == ' ' || *line == '\t') - continue; - if (line[1] != ':') { - (void) fprintf (stderr, "%u:\tERROR: Bad config line (%s)\n", - lineno, line); - continue; + fprintf (stderr, "Checking %s\n\n", path); + + /* Class 0 always exists */ + note_class (0); + + p = filebuf; + while (*(p = skip_ws (p))) { + block_start = p; + p = get_token (p, blocktype, sizeof (blocktype)); + p = skip_ws (p); + if (*p != '{') { + fprintf (stderr, "Line %d: ERROR: Expected '{' after '%s'\n", + line_number (filebuf, block_start), blocktype); + errors++; + break; } + p++; + if (debugflag) - (void) printf ("\n%s\n", line); - (void) fflush (stdout); + printf ("Block: %s (line %d)\n", blocktype, line_number (filebuf, block_start)); + + /* Track fields seen in this block */ + int has_name = 0, has_host = 0, has_port = 0, has_password = 0; + int has_class = 0, has_info = 0, has_line = 0; + int admin_lines = 0; + int class_num = -1; + int name_num = -1; + has_connect_port = 0; + + while (*(p = skip_ws (p)) && *p != '}') { + char *key_pos = p; + p = get_token (p, key, sizeof (key)); + if (!*key) + break; + p = get_token (p, value, sizeof (value)); + p = skip_ws (p); + if (*p == ';') + p++; + else { + fprintf (stderr, "Line %d: WARNING: Missing ';' after '%s %s'\n", + line_number (filebuf, key_pos), key, value); + } - tmp = getfield (line); - if (!tmp) { - (void) fprintf (stderr, "\tERROR: no fields found\n"); - continue; + if (!mycmp (key, "name")) { has_name = 1; name_num = atoi (value); } + else if (!mycmp (key, "host")) has_host = 1; + else if (!mycmp (key, "address")) has_host = 1; + else if (!mycmp (key, "ip")) has_host = 1; + else if (!mycmp (key, "port")) { has_port = 1; has_connect_port = atoi (value); } + else if (!mycmp (key, "password") || !mycmp (key, "passwd")) has_password = 1; + else if (!mycmp (key, "class")) { has_class = 1; class_num = atoi (value); } + else if (!mycmp (key, "info")) has_info = 1; + else if (!mycmp (key, "line")) { has_line = 1; admin_lines++; } + else if (!mycmp (key, "flags")) ; + else if (!mycmp (key, "hub")) ; + else if (!mycmp (key, "ident")) ; + else if (!mycmp (key, "reason")) ; + else if (!mycmp (key, "nick")) has_name = 1; + else if (!mycmp (key, "die")) ; + else if (!mycmp (key, "restart")) ; + else if (!mycmp (key, "pingfreq")) ; + else if (!mycmp (key, "connfreq")) ; + else if (!mycmp (key, "maxlinks")) ; + else if (!mycmp (key, "sendq")) ; + else if (!mycmp (key, "time")) ; + else { + fprintf (stderr, "Line %d: WARNING: Unknown key '%s' in %s block\n", + line_number (filebuf, key_pos), key, blocktype); + } } - aconf->status = CONF_ILLEGAL; - switch (*tmp) { - case 'A': /* Name, e-mail address of administrator */ - aconf->status = CONF_ADMIN; - break; - case 'a': /* of this server. */ - aconf->status = CONF_SADMIN; - break; - case 'C': /* Server where I should try to connect */ - case 'c': /* in case of lp failures */ - ccount++; - aconf->status = CONF_CONNECT_SERVER; - break; - case 'E': /* Blocked DCC file type */ - aconf->status = CONF_DCCBLOCK; - break; - case 'f': /* Temp Z-line time */ - case 'F': - aconf->status = CONF_ZTIME; - break; - case 'H': /* Hub server line */ - case 'h': - aconf->status = CONF_HUB; - break; - case 'I': /* Just plain normal irc client trying */ - case 'i': /* to connect me */ - aconf->status = CONF_CLIENT; - break; - case 'K': /* Kill user line on irc.conf */ - case 'k': - aconf->status = CONF_KILL; - break; - /* Me. Host field is name used for this host */ - /* and port number is the number of the port */ - case 'M': - case 'm': - aconf->status = CONF_ME; - break; - case 'N': /* Server where I should NOT try to */ - case 'n': /* connect in case of lp failures */ - /* but which tries to connect ME */ - ++ncount; - aconf->status = CONF_NOCONNECT_SERVER; - break; - case 'O': - aconf->status = CONF_OPERATOR; - break; - /* Local Operator, (limited privs --SRB) - * Not anymore, OperFlag access levels. -Cabal95 */ - case 'o': - aconf->status = CONF_OPERATOR; - break; - case 'P': /* listen port line */ - case 'p': - aconf->status = CONF_LISTEN_PORT; - break; - case 'Q': /* a server that you don't want in your */ - case 'q': /* network. USE WITH CAUTION! */ - aconf->status = CONF_QUARANTINED_SERVER; - break; - case 'S': /* Service. Same semantics as */ - case 's': /* CONF_OPERATOR */ - aconf->status = CONF_SERVICE; - break; - case 'U': - case 'u': - aconf->status = CONF_UWORLD; - break; - case 'X': - case 'x': - aconf->status = CONF_DRPASS; - break; - case 'Y': - case 'y': - aconf->status = CONF_CLASS; - break; - case 'Z': - case 'z': - aconf->status = CONF_ZAP; - break; - default: - (void) fprintf (stderr, - "%u:\tERROR: unknown conf line letter (%c)\n", - lineno, *tmp); - break; - } + if (*p == '}') + p++; + p = skip_ws (p); + if (*p == ';') + p++; - if (IsIllegal (aconf)) - continue; + /* Validate required fields per block type */ + int bline = line_number (filebuf, block_start); - for (;;) { /* Fake loop, that I can use break here --msa */ - if ((tmp = getfield (NULL)) == NULL) - break; - DupString (aconf->host, tmp); - if ((tmp = getfield (NULL)) == NULL) - break; - DupString (aconf->passwd, tmp); - if ((tmp = getfield (NULL)) == NULL) - break; - DupString (aconf->name, tmp); - if ((tmp = getfield (NULL)) == NULL) - break; - if (aconf->status & CONF_OPERATOR) { - int *i, flag; - char *m = "*"; - /* - * Now we use access flags to define - * what an operator can do with their O. - */ - for (m = (*tmp) ? tmp : m; *m; m++) { - for (i = oper_access; (flag = *i); i += 2) - if (*m == (char) (*(i + 1))) { - aconf->port |= flag; - break; - } - if (flag == 0) - fprintf (stderr, - "%u:\tWARNING: Unknown oper access level '%c'\n", - lineno, *m); - } - if (!(aconf->port & OFLAG_ISGLOBAL)) - aconf->status = CONF_LOCOP; + if (!mycmp (blocktype, "me")) { + if (has_me) { + fprintf (stderr, "Line %d: ERROR: Duplicate me {} block\n", bline); + errors++; + } + has_me = 1; + if (!has_name) { + fprintf (stderr, "Line %d: ERROR: me {} requires 'name'\n", bline); + errors++; + } + if (!has_port) { + fprintf (stderr, "Line %d: WARNING: me {} has no 'port'\n", bline); } - else - aconf->port = atoi (tmp); - if ((tmp = getfield (NULL)) == NULL) - break; - if (!(aconf->status & CONF_CLASS)) - aconf->class = get_class (atoi (tmp)); - break; } - if (!aconf->class && (aconf->status & (CONF_CONNECT_SERVER | - CONF_NOCONNECT_SERVER | - CONF_OPS | CONF_CLIENT))) { - (void) fprintf (stderr, "%u:\tWARNING: No class. Default 0\n", - lineno); - aconf->class = get_class (0); + else if (!mycmp (blocktype, "admin")) { + if (has_admin) { + fprintf (stderr, "Line %d: ERROR: Duplicate admin {} block\n", bline); + errors++; + } + has_admin = 1; + if (admin_lines == 0) { + fprintf (stderr, "Line %d: WARNING: admin {} has no 'line' entries\n", bline); + } } - /* Check for bad Z-lines */ - if (aconf->status == CONF_ZAP) { - char *tempc = aconf->host; - if (!tempc) { - fprintf (stderr, "%u:\tERROR: Bad Z-line\n", lineno); + else if (!mycmp (blocktype, "class")) { + if (!has_name && class_num < 0) { + fprintf (stderr, "Line %d: ERROR: class {} requires 'name'\n", bline); + errors++; + } else { + int cn = name_num >= 0 ? name_num : class_num; + note_class (cn); + } + } + else if (!mycmp (blocktype, "allow")) { + if (!has_host) { + fprintf (stderr, "Line %d: ERROR: allow {} requires 'ip' or 'host'\n", bline); + errors++; + } + if (has_class && !class_exists (class_num)) { + fprintf (stderr, "Line %d: WARNING: allow {} references undefined class %d\n", + bline, class_num); } - for (; *tempc; tempc++) - if ((*tempc >= '0') && (*tempc <= '9')) - goto zap_safe; - fprintf (stderr, "%u:\tERROR: Z-line mask too broad\n", lineno); - zap_safe:; } - /* Check for bad F lines */ - if (aconf->status == CONF_ZTIME) { - if (!((aconf->host) && isdigit (*aconf->host))) - (void) fprintf (stderr, - "%u:\tERROR: F-lines must contain a digit in the first slot\n", - lineno); + else if (!mycmp (blocktype, "listen")) { + if (!has_port) { + fprintf (stderr, "Line %d: ERROR: listen {} requires 'port'\n", bline); + errors++; + } } - /* - ** If conf line is a class definition, create a class entry - ** for it and make the conf_line illegal and delete it. - */ - if (aconf->status & CONF_CLASS) { - if (!aconf->host) { - (void) fprintf (stderr, "\tERROR: no class #\n"); - continue; + else if (!mycmp (blocktype, "oper")) { + if (!has_host) { + fprintf (stderr, "Line %d: ERROR: oper {} requires 'host'\n", bline); + errors++; + } + if (!has_password) { + fprintf (stderr, "Line %d: ERROR: oper {} requires 'password'\n", bline); + errors++; } - if (!tmp) { - (void) fprintf (stderr, - "%u:\tWARNING: missing sendq field\n", - lineno); - (void) fprintf (stderr, "\t\t default: %d\n", MAXSENDQLENGTH); - (void) sprintf (maxsendq, "%d", MAXSENDQLENGTH); + if (!has_name) { + fprintf (stderr, "Line %d: ERROR: oper {} requires 'name'\n", bline); + errors++; } - else - (void) sprintf (maxsendq, "%d", atoi (tmp)); - new_class (atoi (aconf->host)); - aconf->class = get_class (atoi (aconf->host)); - goto print_confline; } - if (aconf->status & CONF_LISTEN_PORT) { - if (!aconf->host) - (void) fprintf (stderr, "\tERROR: %s\n", - "null host field in P-line"); - else if (strchr (aconf->host, '/')) - (void) fprintf (stderr, - "%u:\tWARNING: / present in P-line " - "UNIXPORT configuration no longer supported\n", - lineno); - aconf->class = get_class (0); - goto print_confline; + else if (!mycmp (blocktype, "connect")) { + if (!has_host) { + fprintf (stderr, "Line %d: ERROR: connect {} requires 'host'\n", bline); + errors++; + } + if (!has_password) { + fprintf (stderr, "Line %d: ERROR: connect {} requires 'password'\n", bline); + errors++; + } + if (!has_name) { + fprintf (stderr, "Line %d: ERROR: connect {} requires 'name'\n", bline); + errors++; + } + if (has_class && !class_exists (class_num)) { + fprintf (stderr, "Line %d: WARNING: connect {} references undefined class %d\n", + bline, class_num); + } } - if (aconf->status & CONF_SERVER_MASK && - (!aconf->host || strchr (aconf->host, '*') || - strchr (aconf->host, '?'))) { - (void) fprintf (stderr, "\tERROR: bad host field\n"); - continue; + else if (!mycmp (blocktype, "drpass")) { + /* Optional, no required fields */ } - if (aconf->status & CONF_SERVER_MASK && BadPtr (aconf->passwd)) { - (void) fprintf (stderr, "\tERROR: empty/no password field\n"); - continue; + else if (!mycmp (blocktype, "uworld")) { + if (!has_name) { + fprintf (stderr, "Line %d: ERROR: uworld {} requires 'name'\n", bline); + errors++; + } } - if (aconf->status & CONF_SERVER_MASK && !aconf->name) { - (void) fprintf (stderr, "\tERROR: bad name field\n"); - continue; + else if (!mycmp (blocktype, "general")) { + /* All optional */ } - if (aconf->status & (CONF_SERVER_MASK | CONF_OPS)) - if (!strchr (aconf->host, '@')) { - char *newhost; - int len = 3; /* *@\0 = 3 */ - - len += strlen (aconf->host); - newhost = (char *) MyMalloc (len); - (void) sprintf (newhost, "*@%s", aconf->host); - (void) free (aconf->host); - aconf->host = newhost; + else if (!mycmp (blocktype, "quarantine")) { + if (!has_name) { + fprintf (stderr, "Line %d: WARNING: quarantine {} has no 'nick'\n", bline); } - - if (!aconf->class) - aconf->class = get_class (0); - (void) sprintf (maxsendq, "%d", aconf->class->class); - - if (!aconf->name) - aconf->name = nullfield; - if (!aconf->passwd) - aconf->passwd = nullfield; - if (!aconf->host) - aconf->host = nullfield; - if (aconf->status & (CONF_ME | CONF_ADMIN)) { - if (flags & aconf->status) - (void) fprintf (stderr, - "ERROR: multiple %c-lines\n", - toupper (confchar (aconf->status))); - else - flags |= aconf->status; } - print_confline: - if (debugflag > 8) - (void) printf ("(%d) (%s) (%s) (%s) (%d) (%s)\n", - aconf->status, aconf->host, aconf->passwd, - aconf->name, aconf->port, maxsendq); - (void) fflush (stdout); - if (aconf->status & (CONF_SERVER_MASK | CONF_HUB)) { - aconf->next = ctop; - ctop = aconf; - aconf = NULL; + else if (!mycmp (blocktype, "squar")) { + if (!has_name) { + fprintf (stderr, "Line %d: WARNING: squar {} has no 'name'\n", bline); + } + } + else if (!mycmp (blocktype, "kline")) { + if (!has_host) { + fprintf (stderr, "Line %d: ERROR: kline {} requires 'host'\n", bline); + errors++; + } + } + else if (!mycmp (blocktype, "zline")) { + if (!has_host) { + fprintf (stderr, "Line %d: ERROR: zline {} requires 'address'\n", bline); + errors++; + } + } + else if (!mycmp (blocktype, "service")) { + /* Optional */ + } + else if (!mycmp (blocktype, "dccblock")) { + /* Optional */ + } + else { + fprintf (stderr, "Line %d: WARNING: Unknown block type '%s'\n", + bline, blocktype); } } - printf ("\n"); - (void) close (fd); - return ctop; -} -static aClass * get_class (int cn) -{ - static aClass cls; - int i = numclasses - 1; - - cls.class = -1; - for (; i >= 0; i--) - if (classarr[i] == cn) { - cls.class = cn; - break; - } - if (i == -1) - (void) fprintf (stderr, "\tWARNING: class %d not found\n", cn); - return &cls; -} + if (!has_me) { + fprintf (stderr, "ERROR: No me {} block found (required)\n"); + errors++; + } + if (!has_admin) { + fprintf (stderr, "WARNING: No admin {} block found\n"); + } -static void new_class (int cn) -{ - numclasses++; - if (classarr) - classarr = (int *) realloc (classarr, sizeof (int) * numclasses); + fprintf (stderr, "\n"); + if (errors) + fprintf (stderr, "%d error(s) found.\n", errors); else - classarr = (int *) malloc (sizeof (int)); - classarr[numclasses - 1] = cn; -} + fprintf (stderr, "Config file looks good.\n"); -/* - * field breakup for ircd.conf file. - */ -static char * getfield (char *newline) -{ - static char *line = NULL; - char *end, *field; - - if (newline) - line = newline; - if (line == NULL) - return (NULL); - - field = line; - if ((end = (char *) strchr (line, ':')) == NULL) { - line = NULL; - if ((end = (char *) strchr (field, '\n')) == NULL) - end = field + strlen (field); - } - else - line = end + 1; - *end = '\0'; - return (field); + free (filebuf); + return errors ? -1 : 0; } -static int validate (aConfItem *top) +int main (int argc, char *argv[]) { - Reg aConfItem *aconf, *bconf; - u_int otype = 0, valid = 0; - - if (!top) - return 0; - - for (aconf = top; aconf; aconf = aconf->next) { - if (aconf->status & CONF_MATCH) - continue; - - if (aconf->status & CONF_SERVER_MASK) { - if (aconf->status & CONF_CONNECT_SERVER) - otype = CONF_NOCONNECT_SERVER; - else if (aconf->status & CONF_NOCONNECT_SERVER) - otype = CONF_CONNECT_SERVER; - - for (bconf = top; bconf; bconf = bconf->next) { - if (bconf == aconf || !(bconf->status & otype)) - continue; - if (bconf->class == aconf->class && - !mycmp (bconf->name, aconf->name) && - !mycmp (bconf->host, aconf->host)) { - aconf->status |= CONF_MATCH; - bconf->status |= CONF_MATCH; - break; - } - } - } - else - for (bconf = top; bconf; bconf = bconf->next) { - if ((bconf == aconf) || !(bconf->status & CONF_SERVER_MASK)) - continue; - if (!mycmp (bconf->name, aconf->name)) { - aconf->status |= CONF_MATCH; - break; - } - } + if (chdir (DPATH)) { + perror ("chdir"); + exit (-1); } - - (void) fprintf (stderr, "\n"); - for (aconf = top; aconf; aconf = aconf->next) - if (aconf->status & CONF_MATCH) - valid++; - else - (void) fprintf (stderr, "Unmatched %c:%s:%s:%s\n", - confchar (aconf->status), aconf->host, - aconf->passwd, aconf->name); - return valid ? 0 : -1; -} - -static char confchar (u_int status) -{ - static char letrs[] = "QICNoOMKARYSLPH"; - char *s = letrs; - - status &= ~(CONF_MATCH | CONF_ILLEGAL); - - for (; *s; s++, status >>= 1) - if (status & 1) - return *s; - return '-'; + if (argc > 1 && !strncmp (argv[1], "-d", 2)) { + debugflag = 1; + if (argv[1][2]) + debugflag = atoi (argv[1] + 2); + argc--, argv++; + } + else if (argc > 1 && !strncmp (argv[1], "-h", 2)) { + printf ("chkconf [-d] [conf file]\n"); + printf ("-d = debug mode\n"); + printf ("conf file = path to ircd.conf\n\n"); + exit (0); + } + if (argc > 1) + configfile = argv[1]; + return validate_conf (configfile); } int outofmemory () diff --git a/src/parse.c b/src/parse.c index 883a40a..74af8f6 100644 --- a/src/parse.c +++ b/src/parse.c @@ -381,31 +381,6 @@ int parse (aClient *cptr, char *buffer, char *bufend, struct Message *mptr) #endif } -/* - * field breakup for ircd.conf file. - */ -char *getfield (char *newline) -{ - static char *line = NULL; - char *end, *field; - - if (newline) - line = newline; - if (line == NULL) - return (NULL); - - field = line; - if ((end = (char *) strchr (line, ':')) == NULL) { - line = NULL; - if ((end = (char *) strchr (field, '\n')) == NULL) - end = field + strlen (field); - } - else - line = end + 1; - *end = '\0'; - return (field); -} - static int cancel_clients (aClient *cptr, aClient *sptr, char *cmd) { /* diff --git a/src/s_conf.c b/src/s_conf.c index 58ead6b..77dafc1 100644 --- a/src/s_conf.c +++ b/src/s_conf.c @@ -795,8 +795,6 @@ int openconf () { return open (configfile, O_RDONLY); } -extern char *getfield (); - static int oper_access[] = { ~(OFLAG_ADMIN | OFLAG_SADMIN | OFLAG_ZLINE), '*', OFLAG_LOCAL, 'o', @@ -823,348 +821,512 @@ static int oper_access[] = { }; /* - ** initconf() - ** Read configuration file. - ** - ** returns -1, if file cannot be opened - ** 0, if file opened + * Block config parser. + * + * The config file uses a block-based format: + * blocktype { + * key value; + * key "quoted value"; + * }; + * + * Comments start with # and run to end of line. + */ + +/* Read entire config file into a malloc'd buffer */ +static char *conf_read_file (int fd) +{ + struct stat st; + char *buf; + int n, total = 0; + + if (fstat (fd, &st) < 0) + return NULL; + if (st.st_size > 1048576) + return NULL; + buf = (char *) MyMalloc (st.st_size + 1); + while (total < st.st_size) { + n = read (fd, buf + total, st.st_size - total); + if (n <= 0) + break; + total += n; + } + buf[total] = '\0'; + return buf; +} + +static char *conf_skip_ws (char *p) +{ + while (*p) { + if (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') + p++; + else if (*p == '#') { + while (*p && *p != '\n') + p++; + } else + break; + } + return p; +} + +/* Parse a token (word or quoted string). Writes into dst. */ +static char *conf_get_token (char *p, char *dst, int dstsize) +{ + int i = 0; + + p = conf_skip_ws (p); + if (*p == '"') { + p++; + while (*p && *p != '"' && i < dstsize - 1) { + if (*p == '\\' && *(p + 1)) { + p++; + switch (*p) { + case 'n': dst[i++] = '\n'; break; + case 't': dst[i++] = '\t'; break; + case '\\': dst[i++] = '\\'; break; + case '"': dst[i++] = '"'; break; + default: dst[i++] = *p; break; + } + p++; + } else + dst[i++] = *p++; + } + if (*p == '"') + p++; + } else { + while (*p && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n' + && *p != ';' && *p != '{' && *p != '}' && i < dstsize - 1) + dst[i++] = *p++; + } + dst[i] = '\0'; + return p; +} + +static int conf_parse_oper_flags (char *flags) +{ + int result = 0; + int *i, flag; + char *m; + + for (m = flags; *m; m++) + for (i = oper_access; (flag = *i); i += 2) + if (*m == (char) (*(i + 1))) { + result |= flag; + break; + } + return result; +} + +/* Add an aConfItem to the global conf list with post-processing */ +static void conf_add_item (aConfItem *aconf, int opt) +{ + if (aconf->status == CONF_ZAP) { + char *tempc = aconf->host; + if (!tempc) { + free_conf (aconf); + return; + } + for (; *tempc; tempc++) + if (*tempc >= '0' && *tempc <= '9') + goto zap_safe; + free_conf (aconf); + return; + zap_safe:; + } + if (aconf->status & (CONF_CLIENT_MASK | CONF_LISTEN_PORT)) { + if (Class (aconf) == 0) + Class (aconf) = find_class (0); + if (MaxLinks (Class (aconf)) < 0) + Class (aconf) = find_class (0); + } + if (aconf->status & (CONF_LISTEN_PORT | CONF_CLIENT)) { + aConfItem *bconf; + + if ((bconf = find_conf_entry (aconf, aconf->status))) { + delist_conf (bconf); + bconf->status &= ~CONF_ILLEGAL; + if (aconf->status == CONF_CLIENT) { + bconf->class->links -= bconf->clients; + bconf->class = aconf->class; + if (bconf->class) + bconf->class->links += bconf->clients; + } + free_conf (aconf); + aconf = bconf; + } + else if (aconf->host && aconf->status == CONF_LISTEN_PORT) + (void) add_listener (aconf); + } + if (aconf->status & CONF_SERVER_MASK) + if (!aconf->host || strchr (aconf->host, '*') || + strchr (aconf->host, '?') || !aconf->name) { + free_conf (aconf); + return; + } + if (aconf->status & (CONF_SERVER_MASK | CONF_LOCOP | CONF_OPERATOR)) + if (aconf->host && !strchr (aconf->host, '@') && *aconf->host != '/') { + char *newhost; + int len = 3; + + len += strlen (aconf->host); + newhost = (char *) MyMalloc (len); + (void) sprintf (newhost, "*@%s", aconf->host); + MyFree (aconf->host); + aconf->host = newhost; + } + if (aconf->status & CONF_SERVER_MASK) { + if (BadPtr (aconf->passwd)) { + free_conf (aconf); + return; + } + if (!(opt & BOOT_QUICK)) + (void) lookup_confhost (aconf); + } + if (aconf->status == CONF_ME) { + strncpyzt (me.info, aconf->name, sizeof (me.info)); + if (me.name[0] == '\0' && aconf->host[0]) + strncpyzt (me.name, aconf->host, sizeof (me.name)); + if (aconf->passwd[0] && (aconf->passwd[0] != '*')) + me.ip.s_addr = inet_addr (aconf->passwd); + else + me.ip.s_addr = INADDR_ANY; + if (portnum < 0 && aconf->port >= 0) + portnum = aconf->port; + } + if (aconf->status == CONF_KILL) + aconf->tmpconf = KLINE_PERM; + (void) collapse (aconf->host); + (void) collapse (aconf->name); + Debug ((DEBUG_NOTICE, + "Read Init: (%d) (%s) (%s) (%s) (%d) (%d)", + aconf->status, aconf->host, aconf->passwd, + aconf->name, aconf->port, Class (aconf))); + aconf->next = conf; + conf = aconf; +} + +/* + * initconf() + * Read block-based configuration file. + * + * returns -1, if file cannot be opened + * 0, if file opened */ #define MAXCONFLINKS 150 +#define CONF_TOKLEN 512 +#define CONF_MAX_HOSTS 256 int initconf (int opt) { - static char quotes[9][2] = { - {'b', '\b'}, - {'f', '\f'}, - {'n', '\n'}, - {'r', '\r'}, - {'t', '\t'}, - {'v', '\v'}, - {'\\', '\\'}, - {0, 0} - }; - char *tmp, *s; - int fd, i; - char line[512], c[80]; - int ccount = 0, ncount = 0; - aConfItem *aconf = NULL; + int fd; + char *filebuf, *p; + char blocktype[CONF_TOKLEN], key[CONF_TOKLEN], value[CONF_TOKLEN]; is_hub = 0; Debug ((DEBUG_DEBUG, "initconf(): ircd.conf = %s", configfile)); - if ((fd = openconf ()) == -1) { + if ((fd = openconf ()) == -1) return -1; - } - (void) dgets (-1, NULL, 0); /* make sure buffer is at empty pos */ - while ((i = dgets (fd, line, sizeof (line) - 1)) > 0) { - line[i] = '\0'; - if (line[i-2] == '\r') { /* Strip DOS type end of lines */ - line[i-2] = '\n'; - line[i-1] = '\0'; + + filebuf = conf_read_file (fd); + (void) close (fd); + if (!filebuf) + return -1; + + p = filebuf; + while (*(p = conf_skip_ws (p))) { + p = conf_get_token (p, blocktype, sizeof (blocktype)); + p = conf_skip_ws (p); + if (*p != '{') { + Debug ((DEBUG_ERROR, "Expected '{' after block type '%s'", blocktype)); + break; } - if ((tmp = (char *) strchr (line, '\n'))) - *tmp = 0; - else - while (dgets (fd, c, sizeof (c) - 1) > 0) - if ((tmp = (char *) strchr (c, '\n'))) { - *tmp = 0; - break; - } - /* - * Do quoting of characters and # detection. - */ - for (tmp = line; *tmp; tmp++) { - if (*tmp == '\\') { - for (i = 0; quotes[i][0]; i++) - if (quotes[i][0] == *(tmp + 1)) { - *tmp = quotes[i][1]; - break; - } - if (!quotes[i][0]) - *tmp = *(tmp + 1); - if (!*(tmp + 1)) - break; + p++; + + /* Per-block field storage */ + char b_name[CONF_TOKLEN] = ""; + char b_host[CONF_TOKLEN] = ""; + char b_passwd[CONF_TOKLEN] = ""; + char b_info[CONF_TOKLEN] = ""; + char b_flags[CONF_TOKLEN] = ""; + char b_hub[CONF_TOKLEN] = ""; + char b_die[CONF_TOKLEN] = ""; + char b_restart[CONF_TOKLEN] = ""; + char b_admin[3][CONF_TOKLEN]; + char b_hosts[CONF_MAX_HOSTS][CONF_TOKLEN]; + int b_port = 0, b_class = 0; + int b_admin_count = 0, b_host_count = 0; + int b_hub_mode = 0; + int b_pingfreq = 0, b_connfreq = 0, b_maxlinks = 0, b_sendq = 0; + int b_zline_time = 0; + + memset (b_admin, 0, sizeof (b_admin)); + + /* Parse key-value pairs */ + while (*(p = conf_skip_ws (p)) && *p != '}') { + p = conf_get_token (p, key, sizeof (key)); + if (!*key) + break; + p = conf_get_token (p, value, sizeof (value)); + p = conf_skip_ws (p); + if (*p == ';') + p++; + + if (!mycmp (key, "name")) + strncpyzt (b_name, value, sizeof (b_name)); + else if (!mycmp (key, "host")) { + /* In allow{} blocks, "host" is the hostname mask (-> b_name), + * while "ip" is the IP mask (-> b_host). In kline{} blocks, + * "host" can be repeated for multi-host bans. */ + if (!mycmp (blocktype, "allow")) + strncpyzt (b_name, value, sizeof (b_name)); else - for (s = tmp; (*s = *(s + 1)); s++); + strncpyzt (b_host, value, sizeof (b_host)); + if (b_host_count < CONF_MAX_HOSTS) + strncpyzt (b_hosts[b_host_count++], value, CONF_TOKLEN); } - else if (*tmp == '#') - *tmp = '\0'; - } - if (!*line || line[0] == '#' || line[0] == '\n' || - line[0] == ' ' || line[0] == '\t') - continue; - /* Could we test if it's conf line at all? -Vesa */ - if (line[1] != ':') { - Debug ((DEBUG_ERROR, "Bad config line: %s", line)); - continue; + else if (!mycmp (key, "password") || !mycmp (key, "passwd")) + strncpyzt (b_passwd, value, sizeof (b_passwd)); + else if (!mycmp (key, "address")) + strncpyzt (b_host, value, sizeof (b_host)); + else if (!mycmp (key, "port")) + b_port = atoi (value); + else if (!mycmp (key, "class")) + b_class = atoi (value); + else if (!mycmp (key, "flags")) + strncpyzt (b_flags, value, sizeof (b_flags)); + else if (!mycmp (key, "info")) + strncpyzt (b_info, value, sizeof (b_info)); + else if (!mycmp (key, "line")) { + if (b_admin_count < 3) + strncpyzt (b_admin[b_admin_count++], value, CONF_TOKLEN); + } + else if (!mycmp (key, "ip")) + strncpyzt (b_host, value, sizeof (b_host)); + else if (!mycmp (key, "hub")) { + if (!mycmp (blocktype, "general")) + b_hub_mode = (!mycmp (value, "yes") || !mycmp (value, "1")); + else + strncpyzt (b_hub, value, sizeof (b_hub)); + } + else if (!mycmp (key, "ident")) + strncpyzt (b_name, value, sizeof (b_name)); + else if (!mycmp (key, "reason")) + strncpyzt (b_passwd, value, sizeof (b_passwd)); + else if (!mycmp (key, "nick")) + strncpyzt (b_name, value, sizeof (b_name)); + else if (!mycmp (key, "die")) + strncpyzt (b_die, value, sizeof (b_die)); + else if (!mycmp (key, "restart")) + strncpyzt (b_restart, value, sizeof (b_restart)); + else if (!mycmp (key, "pingfreq")) + b_pingfreq = atoi (value); + else if (!mycmp (key, "connfreq")) + b_connfreq = atoi (value); + else if (!mycmp (key, "maxlinks")) + b_maxlinks = atoi (value); + else if (!mycmp (key, "sendq")) + b_sendq = atoi (value); + else if (!mycmp (key, "time")) + b_zline_time = atoi (value); + else + Debug ((DEBUG_ERROR, "Unknown key '%s' in block '%s'", + key, blocktype)); } - if (aconf) - free_conf (aconf); - aconf = make_conf (); - tmp = getfield (line); - if (!tmp) - continue; - switch (*tmp) { - case 'A': /* Name, e-mail address of administrator */ + if (*p == '}') + p++; + p = conf_skip_ws (p); + if (*p == ';') + p++; + + /* Create aConfItem(s) from block */ + + if (!mycmp (blocktype, "me")) { + /* M:servername:bindaddr:description:port */ + aConfItem *aconf = make_conf (); + aconf->status = CONF_ME; + DupString (aconf->host, b_name); + DupString (aconf->passwd, b_host[0] ? b_host : "*"); + DupString (aconf->name, b_info[0] ? b_info : b_name); + aconf->port = b_port; + conf_add_item (aconf, opt); + } + else if (!mycmp (blocktype, "admin")) { + /* A:line1:line2:line3 */ + aConfItem *aconf = make_conf (); aconf->status = CONF_ADMIN; - break; - case 'a': /* of this server. */ - aconf->status = CONF_SADMIN; - break; - case 'C': /* Server where I should try to connect */ - case 'c': /* in case of lp failures */ - ccount++; - aconf->status = CONF_CONNECT_SERVER; - break; - case 'e': - case 'E': /* Blocking of DCC transfers -GZ */ - aconf->status = CONF_DCCBLOCK; - break; - case 'f': - case 'F': - aconf->status = CONF_ZTIME; - break; - case 'G': - case 'g': - /* General config options */ - aconf->status = CONF_CONFIG; - break; - case 'H': /* Hub server line */ - case 'h': - aconf->status = CONF_HUB; - break; - case 'I': /* Just plain normal irc client trying */ - case 'i': /* to connect me */ + DupString (aconf->host, b_admin_count > 0 ? b_admin[0] : ""); + DupString (aconf->passwd, b_admin_count > 1 ? b_admin[1] : ""); + DupString (aconf->name, b_admin_count > 2 ? b_admin[2] : ""); + conf_add_item (aconf, opt); + } + else if (!mycmp (blocktype, "class")) { + /* Y:class:pingfreq:connfreq:maxlinks:sendq */ + int classnum = b_class ? b_class : atoi (b_name); + add_class (classnum, b_pingfreq, b_connfreq, b_maxlinks, b_sendq); + } + else if (!mycmp (blocktype, "allow")) { + /* I:ipmask:password:hostmask:port:class + * ip= -> aconf->host, host= -> aconf->name */ + aConfItem *aconf = make_conf (); aconf->status = CONF_CLIENT; - break; - case 'K': /* Kill user line on irc.conf */ - case 'k': - aconf->status = CONF_KILL; - break; - /* Me. Host field is name used for this host */ - /* and port number is the number of the port */ - case 'M': - case 'm': - aconf->status = CONF_ME; - break; - case 'N': /* Server where I should NOT try to */ - case 'n': /* connect in case of lp failures */ - /* but which tries to connect ME */ - ++ncount; - aconf->status = CONF_NOCONNECT_SERVER; - break; - case 'O': - case 'o': - aconf->status = CONF_OPERATOR; - break; - case 'P': /* listen port line */ - case 'p': + DupString (aconf->host, b_host[0] ? b_host : b_name); + DupString (aconf->passwd, b_passwd); + DupString (aconf->name, b_name[0] ? b_name : b_host); + aconf->port = b_port; + if (b_class) + Class (aconf) = find_class (b_class); + conf_add_item (aconf, opt); + } + else if (!mycmp (blocktype, "listen")) { + /* P:address:*:*:port */ + aConfItem *aconf = make_conf (); aconf->status = CONF_LISTEN_PORT; - break; - case 'Q': /* reserved nicks */ - aconf->status = CONF_QUARANTINED_NICK; - break; - case 'q': /* a server that you don't want in your */ - /* network. USE WITH CAUTION! */ - aconf->status = CONF_QUARANTINED_SERVER; - break; - case 'S': /* Service. Same semantics as */ - case 's': /* CONF_OPERATOR */ - aconf->status = CONF_SERVICE; - break; - case 'U': /* Underworld server, allowed to hack modes */ - case 'u': /* *Every* server on the net must define the same !!! */ - aconf->status = CONF_UWORLD; - break; - case 'Y': - case 'y': - aconf->status = CONF_CLASS; - break; - case 'Z': - case 'z': - aconf->status = CONF_ZAP; - break; - case 'X': - case 'x': + DupString (aconf->host, b_host[0] ? b_host : ""); + DupString (aconf->passwd, "*"); + DupString (aconf->name, "*"); + aconf->port = b_port; + conf_add_item (aconf, opt); + } + else if (!mycmp (blocktype, "oper")) { + /* O:host:passwd:nick:flags:class */ + aConfItem *aconf = make_conf (); + aconf->status = CONF_OPERATOR; + DupString (aconf->host, b_host); + DupString (aconf->passwd, b_passwd); + DupString (aconf->name, b_name); + aconf->port = conf_parse_oper_flags (b_flags[0] ? b_flags : "*"); + if (!(aconf->port & OFLAG_ISGLOBAL)) + aconf->status = CONF_LOCOP; + if (b_class) + Class (aconf) = find_class (b_class); + conf_add_item (aconf, opt); + } + else if (!mycmp (blocktype, "drpass")) { + /* X:diepass:restartpass */ + aConfItem *aconf = make_conf (); aconf->status = CONF_DRPASS; - break; - default: - Debug ((DEBUG_ERROR, "Error in config file: %s", line)); - break; + DupString (aconf->host, b_die); + DupString (aconf->passwd, b_restart); + DupString (aconf->name, ""); + conf_add_item (aconf, opt); } - if (IsIllegal (aconf)) - continue; - - for (;;) { /* Parse remaining fields; break on missing field */ - /* X: line format is X:restartpass:diepass - */ - if ((tmp = getfield (NULL)) == NULL) - break; - DupString (aconf->host, tmp); - if ((tmp = getfield (NULL)) == NULL) - break; - DupString (aconf->passwd, tmp); - if ((tmp = getfield (NULL)) == NULL) - break; - DupString (aconf->name, tmp); - if ((tmp = getfield (NULL)) == NULL) - break; - if (aconf->status & CONF_OPS) { - int *i, flag; - char *m = "*"; - /* - * Now we use access flags to define - * what an operator can do with their O. - */ - for (m = (*tmp) ? tmp : m; *m; m++) { - for (i = oper_access; (flag = *i); i += 2) - if (*m == (char) (*(i + 1))) { - aconf->port |= flag; - break; - } - } - if (!(aconf->port & OFLAG_ISGLOBAL)) - aconf->status = CONF_LOCOP; + else if (!mycmp (blocktype, "connect")) { + /* Creates C-line + N-line, optionally H-line */ + aConfItem *cconf = make_conf (); + aConfItem *nconf = make_conf (); + cconf->status = CONF_CONNECT_SERVER; + nconf->status = CONF_NOCONNECT_SERVER; + DupString (cconf->host, b_host); + DupString (cconf->passwd, b_passwd); + DupString (cconf->name, b_name); + cconf->port = b_port; + if (b_class) + Class (cconf) = find_class (b_class); + DupString (nconf->host, b_host); + DupString (nconf->passwd, b_passwd); + DupString (nconf->name, b_name); + nconf->port = 0; + if (b_class) + Class (nconf) = find_class (b_class); + conf_add_item (cconf, opt); + conf_add_item (nconf, opt); + if (b_hub[0]) { + aConfItem *hconf = make_conf (); + hconf->status = CONF_HUB; + DupString (hconf->host, b_hub); + DupString (hconf->passwd, ""); + DupString (hconf->name, b_name); + conf_add_item (hconf, opt); } - else - aconf->port = atoi (tmp); - if ((tmp = getfield (NULL)) == NULL) - break; - Class (aconf) = find_class (atoi (tmp)); - break; } - /* - ** If conf line is a general config, just - ** see if we recognize the keyword, and set - ** the appropriate global. We don't use a "standard" - ** config link here, because these are things which need - ** to be tested SO often that a simple global test - ** is much better! -Aeto - */ - if ((aconf->status & CONF_CONFIG) == CONF_CONFIG) { - if (aconf->host && !mycmp (aconf->host, "hub")) - is_hub = 1; - continue; + else if (!mycmp (blocktype, "uworld")) { + /* U:name:*:* */ + aConfItem *aconf = make_conf (); + aconf->status = CONF_UWORLD; + DupString (aconf->host, b_name); + DupString (aconf->passwd, "*"); + DupString (aconf->name, "*"); + conf_add_item (aconf, opt); } - /* Check if Z:line time -taz */ - if (aconf->status & CONF_ZTIME) { - if ((aconf->host) && isdigit (*aconf->host)) - socks_zline_time = (atoi (aconf->host) * 60); - continue; + else if (!mycmp (blocktype, "general")) { + if (b_hub_mode) + is_hub = 1; + if (b_zline_time) + socks_zline_time = b_zline_time * 60; } - /* Check for bad Z-lines masks as they are *very* dangerous - if not correct!!! */ - if (aconf->status == CONF_ZAP) { - char *tempc = aconf->host; - if (!tempc) { - free_conf (aconf); - aconf = NULL; - continue; - } - for (; *tempc; tempc++) - if ((*tempc >= '0') && (*tempc <= '9')) - goto zap_safe; - free_conf (aconf); - aconf = NULL; - continue; - zap_safe:; + else if (!mycmp (blocktype, "quarantine")) { + /* Q::reason:nick */ + aConfItem *aconf = make_conf (); + aconf->status = CONF_QUARANTINED_NICK; + DupString (aconf->host, ""); + DupString (aconf->passwd, b_passwd); + DupString (aconf->name, b_name); + conf_add_item (aconf, opt); } - /* - ** If conf line is a class definition, create a class entry - ** for it and make the conf_line illegal and delete it. - */ - if (aconf->status & CONF_CLASS) { - add_class (atoi (aconf->host), atoi (aconf->passwd), - atoi (aconf->name), aconf->port, tmp ? atoi (tmp) : 0); - continue; + else if (!mycmp (blocktype, "squar")) { + /* q::reason:servername */ + aConfItem *aconf = make_conf (); + aconf->status = CONF_QUARANTINED_SERVER; + DupString (aconf->host, ""); + DupString (aconf->passwd, b_passwd); + DupString (aconf->name, b_name); + conf_add_item (aconf, opt); } - /* - ** associate each conf line with a class by using a pointer - ** to the correct class record. -avalon - */ - if (aconf->status & (CONF_CLIENT_MASK | CONF_LISTEN_PORT)) { - if (Class (aconf) == 0) - Class (aconf) = find_class (0); - if (MaxLinks (Class (aconf)) < 0) - Class (aconf) = find_class (0); - } - if (aconf->status & (CONF_LISTEN_PORT | CONF_CLIENT)) { - aConfItem *bconf; - - if ((bconf = find_conf_entry (aconf, aconf->status))) { - delist_conf (bconf); - bconf->status &= ~CONF_ILLEGAL; - if (aconf->status == CONF_CLIENT) { - bconf->class->links -= bconf->clients; - bconf->class = aconf->class; - if (bconf->class) - bconf->class->links += bconf->clients; + else if (!mycmp (blocktype, "kline")) { + /* K:host:reason:ident - one aConfItem per host */ + int i; + if (b_host_count == 0) { + aConfItem *aconf = make_conf (); + aconf->status = CONF_KILL; + DupString (aconf->host, b_host); + DupString (aconf->passwd, b_passwd); + DupString (aconf->name, b_name[0] ? b_name : "*"); + conf_add_item (aconf, opt); + } else { + for (i = 0; i < b_host_count; i++) { + aConfItem *aconf = make_conf (); + aconf->status = CONF_KILL; + DupString (aconf->host, b_hosts[i]); + DupString (aconf->passwd, b_passwd); + DupString (aconf->name, b_name[0] ? b_name : "*"); + conf_add_item (aconf, opt); } - free_conf (aconf); - aconf = bconf; } - else if (aconf->host && aconf->status == CONF_LISTEN_PORT) - (void) add_listener (aconf); } - if (aconf->status & CONF_SERVER_MASK) - if (ncount > MAXCONFLINKS || ccount > MAXCONFLINKS || - !aconf->host || strchr (aconf->host, '*') || - strchr (aconf->host, '?') || !aconf->name) - continue; - - if (aconf->status & (CONF_SERVER_MASK | CONF_LOCOP | CONF_OPERATOR)) - if (!strchr (aconf->host, '@') && *aconf->host != '/') { - char *newhost; - int len = 3; /* *@\0 = 3 */ - - len += strlen (aconf->host); - newhost = (char *) MyMalloc (len); - (void) sprintf (newhost, "*@%s", aconf->host); - MyFree (aconf->host); - aconf->host = newhost; - } - if (aconf->status & CONF_SERVER_MASK) { - if (BadPtr (aconf->passwd)) - continue; - else if (!(opt & BOOT_QUICK)) - (void) lookup_confhost (aconf); + else if (!mycmp (blocktype, "zline")) { + /* Z:address:reason:* */ + aConfItem *aconf = make_conf (); + aconf->status = CONF_ZAP; + DupString (aconf->host, b_host); + DupString (aconf->passwd, b_passwd); + DupString (aconf->name, "*"); + conf_add_item (aconf, opt); } - /* - ** Own port and name cannot be changed after the startup. - ** (or could be allowed, but only if all links are closed - ** first). - ** Configuration info does not override the name and port - ** if previously defined. Note, that "info"-field can be - ** changed by "/rehash". - */ - if (aconf->status == CONF_ME) { - strncpyzt (me.info, aconf->name, sizeof (me.info)); - if (me.name[0] == '\0' && aconf->host[0]) - strncpyzt (me.name, aconf->host, sizeof (me.name)); - - if (aconf->passwd[0] && (aconf->passwd[0] != '*')) - me.ip.s_addr = inet_addr (aconf->passwd); - else - me.ip.s_addr = INADDR_ANY; - if (portnum < 0 && aconf->port >= 0) - portnum = aconf->port; - } - if (aconf->status == CONF_KILL) - aconf->tmpconf = KLINE_PERM; - (void) collapse (aconf->host); - (void) collapse (aconf->name); - Debug ((DEBUG_NOTICE, - "Read Init: (%d) (%s) (%s) (%s) (%d) (%d)", - aconf->status, aconf->host, aconf->passwd, - aconf->name, aconf->port, Class (aconf))); - aconf->next = conf; - conf = aconf; - aconf = NULL; + else if (!mycmp (blocktype, "service")) { + /* S:hostmask:*:name */ + aConfItem *aconf = make_conf (); + aconf->status = CONF_SERVICE; + DupString (aconf->host, b_host); + DupString (aconf->passwd, "*"); + DupString (aconf->name, b_name); + if (b_class) + Class (aconf) = find_class (b_class); + conf_add_item (aconf, opt); + } + else if (!mycmp (blocktype, "dccblock")) { + /* E:filename:*:* */ + aConfItem *aconf = make_conf (); + aconf->status = CONF_DCCBLOCK; + DupString (aconf->host, b_host[0] ? b_host : b_name); + DupString (aconf->passwd, "*"); + DupString (aconf->name, "*"); + conf_add_item (aconf, opt); + } + else + Debug ((DEBUG_ERROR, "Unknown block type '%s'", blocktype)); } - if (aconf) - free_conf (aconf); - (void) dgets (-1, NULL, 0); /* make sure buffer is at empty pos */ - (void) close (fd); + + MyFree (filebuf); check_class (); nextping = nextconnect = time (NULL); return 0; diff --git a/tools/convert-conf.py b/tools/convert-conf.py new file mode 100755 index 0000000..180fe6b --- /dev/null +++ b/tools/convert-conf.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +""" +Convert legacy colon-delimited ircd.conf to block-based format. + +Usage: python3 convert-conf.py < old-ircd.conf > new-ircd.conf + +This is a one-time migration tool. The new block format is the only +format supported by the ircd going forward. +""" + +import sys +import re + + +def parse_old_line(line): + """Parse a legacy colon-delimited config line into type and fields.""" + line = line.rstrip('\n\r') + if not line or line.startswith('#') or line[0] in (' ', '\t'): + return None, None, line + if len(line) < 2 or line[1] != ':': + return None, None, line + linetype = line[0] + fields = line[2:].split(':') + return linetype, fields, line + + +def quote_if_needed(s): + """Quote a string if it contains spaces or special characters.""" + if not s: + return '""' + if ' ' in s or '\t' in s or '"' in s or ';' in s or '{' in s or '}' in s: + return '"' + s.replace('\\', '\\\\').replace('"', '\\"') + '"' + return s + + +def convert_line(linetype, fields): + """Convert a parsed legacy line to block format string.""" + # Pad fields to at least 5 elements + while len(fields) < 5: + fields.append('') + + host = fields[0] + passwd = fields[1] + name = fields[2] + port_or_flags = fields[3] + classnum = fields[4] + + if linetype == 'M': + lines = ['me {'] + lines.append(f' name {quote_if_needed(host)};') + if passwd and passwd != '*': + lines.append(f' address {quote_if_needed(passwd)};') + else: + lines.append(' address *;') + lines.append(f' info {quote_if_needed(name)};') + if port_or_flags: + lines.append(f' port {port_or_flags};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'A': + # A:line1:line2:line3 (free-form admin info) + lines = ['admin {'] + if host: + lines.append(f' line {quote_if_needed(host)};') + if passwd: + lines.append(f' line {quote_if_needed(passwd)};') + if name: + lines.append(f' line {quote_if_needed(name)};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'Y': + # Y:class:pingfreq:connfreq:maxlinks:sendq + lines = ['class {'] + lines.append(f' name {host};') + if passwd: + lines.append(f' pingfreq {passwd};') + if name: + lines.append(f' connfreq {name};') + if port_or_flags: + lines.append(f' maxlinks {port_or_flags};') + if classnum: + lines.append(f' sendq {classnum};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'I': + # I:ipmask:password:hostmask:unused:class + lines = ['allow {'] + lines.append(f' ip {quote_if_needed(host)};') + if name: + lines.append(f' host {quote_if_needed(name)};') + if passwd: + lines.append(f' password {quote_if_needed(passwd)};') + if classnum: + lines.append(f' class {classnum};') + if port_or_flags and port_or_flags != '0': + lines.append(f' port {port_or_flags};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'P': + # P:address:*:*:port + lines = ['listen {'] + if host: + lines.append(f' address {quote_if_needed(host)};') + if port_or_flags: + lines.append(f' port {port_or_flags};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'O' or linetype == 'o': + # O:host:passwd:nick:flags:class + lines = ['oper {'] + lines.append(f' host {quote_if_needed(host)};') + lines.append(f' password {quote_if_needed(passwd)};') + lines.append(f' name {quote_if_needed(name)};') + if port_or_flags: + lines.append(f' flags {port_or_flags};') + if classnum: + lines.append(f' class {classnum};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'X': + # X:diepass:restartpass + lines = ['drpass {'] + lines.append(f' die {quote_if_needed(host)};') + lines.append(f' restart {quote_if_needed(passwd)};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'U': + # U:servername:*:* + lines = ['uworld {'] + lines.append(f' name {quote_if_needed(host)};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'Q': + # Q::reason:nick + lines = ['quarantine {'] + lines.append(f' nick {quote_if_needed(name)};') + if passwd: + lines.append(f' reason {quote_if_needed(passwd)};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'q': + # q::reason:server + lines = ['squar {'] + lines.append(f' name {quote_if_needed(name)};') + if passwd: + lines.append(f' reason {quote_if_needed(passwd)};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'K' or linetype == 'k': + # K:host:reason:ident + lines = ['kline {'] + lines.append(f' host {quote_if_needed(host)};') + if name: + lines.append(f' ident {quote_if_needed(name)};') + if passwd: + lines.append(f' reason {quote_if_needed(passwd)};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'Z' or linetype == 'z': + # Z:address:reason:* + lines = ['zline {'] + lines.append(f' address {quote_if_needed(host)};') + if passwd: + lines.append(f' reason {quote_if_needed(passwd)};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'G' or linetype == 'g': + # G:option + if host and host.lower() == 'hub': + return 'general {\n hub yes;\n};' + return f'# Unrecognized G-line option: {host}' + + elif linetype == 'F' or linetype == 'f': + # F:minutes + if host: + return f'general {{\n time {host};\n}};' + return '' + + elif linetype == 'S' or linetype == 's': + # S:hostmask:*:name + lines = ['service {'] + lines.append(f' host {quote_if_needed(host)};') + lines.append(f' name {quote_if_needed(name)};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'E' or linetype == 'e': + # E:filename:*:* + lines = ['dccblock {'] + lines.append(f' name {quote_if_needed(host)};') + lines.append('};') + return '\n'.join(lines) + + elif linetype == 'a': + # a: services admin - same as A but CONF_SADMIN + # Rarely used, just note it + return f'# Services admin (a-line): {host}:{passwd}:{name}' + + else: + return None + + +def main(): + """Convert old ircd.conf from stdin to new format on stdout.""" + # Collect C/N/H lines to merge into connect{} blocks + c_lines = {} # keyed by server name + n_lines = {} + h_lines = {} # keyed by server name + other_output = [] + comments = [] + + input_lines = sys.stdin.readlines() + + # First pass: collect C, N, H lines; convert everything else + for raw_line in input_lines: + linetype, fields, original = parse_old_line(raw_line) + + if linetype is None: + # Comment or blank line + if original.startswith('#'): + comments.append(original) + else: + comments.append(original) + continue + + # Flush accumulated comments before this block + if comments: + for c in comments: + other_output.append(c) + comments = [] + + while len(fields) < 5: + fields.append('') + + if linetype in ('C', 'c'): + # C:host:passwd:servername:port:class + sname = fields[2] + c_lines[sname] = fields + elif linetype in ('N', 'n'): + # N:host:passwd:servername:unused:class + sname = fields[2] + n_lines[sname] = fields + elif linetype in ('H', 'h'): + # H:hubmask:unused:servername + sname = fields[2] + h_lines[sname] = fields + else: + result = convert_line(linetype, fields) + if result is not None: + other_output.append(result) + else: + other_output.append(f'# Unconverted: {original}') + + # Flush trailing comments + if comments: + for c in comments: + other_output.append(c) + + # Second pass: merge C/N/H into connect{} blocks + connect_blocks = [] + all_servers = set(list(c_lines.keys()) + list(n_lines.keys())) + for sname in sorted(all_servers): + c = c_lines.get(sname) + n = n_lines.get(sname) + h = h_lines.get(sname) + + lines = ['connect {'] + # Prefer C-line fields, fall back to N-line + src = c if c else n + if src: + lines.append(f' host {quote_if_needed(src[0])};') + lines.append(f' password {quote_if_needed(src[1])};') + lines.append(f' name {quote_if_needed(src[2])};') + if c and c[3] and c[3] != '0': + lines.append(f' port {c[3]};') + else: + lines.append(' port 0;') + # Class from C-line or N-line + classnum = '' + if c and c[4]: + classnum = c[4] + elif n and n[4]: + classnum = n[4] + if classnum: + lines.append(f' class {classnum};') + if h: + lines.append(f' hub {quote_if_needed(h[0])};') + lines.append('};') + connect_blocks.append('\n'.join(lines)) + + # Output everything + print('# Converted from legacy colon-delimited format') + print('#') + print() + for item in other_output: + print(item) + if connect_blocks: + print() + print('# Server connections (merged from C/N/H lines)') + for block in connect_blocks: + print() + print(block) + + +if __name__ == '__main__': + main()