added basic ssl support to ircu
[ircu2.10.12-pk.git] / ircd / ircd_parser.y
index 758e080a5b4f3e3d89818bf854ca70d4a1914d1d..83cab62fb0483bdbb8fc36f4adf6b1bd9e2b0109 100644 (file)
 #include <stdio.h>
 #include <string.h>
 #include <arpa/inet.h>
+
 #define MAX_STRINGS 80 /* Maximum number of feature params. */
+#define USE_IPV4 (1 << 16)
+#define USE_IPV6 (1 << 17)
+
   extern struct LocalConf   localConf;
   extern struct DenyConf*   denyConfList;
   extern struct CRuleConf*  cruleConfList;
@@ -70,7 +74,9 @@
   /* Now all the globals we need :/... */
   int tping, tconn, maxlinks, sendq, port, invert, stringno, flags;
   char *name, *pass, *host, *ip, *username, *origin, *hub_limit;
+  struct SLink *hosts;
   char *stringlist[MAX_STRINGS];
+  struct ListenerFlags listen_flags;
   struct ConnectionClass *c_class;
   struct DenyConf *dconf;
   struct ServerConf *sconf;
@@ -87,6 +93,16 @@ static void parse_error(char *pattern,...) {
   yyerror(error_buffer);
 }
 
+static void free_slist(struct SLink **link) {
+  struct SLink *next;
+  while (*link != NULL) {
+    next = (*link)->next;
+    MyFree((*link)->value.cp);
+    free_link(*link);
+    *link = next;
+  }
+}
+
 %}
 
 %token <text> QSTRING
@@ -157,18 +173,25 @@ static void parse_error(char *pattern,...) {
 %token FAST
 %token AUTOCONNECT
 %token PROGRAM
+%token TOK_IPV4 TOK_IPV6
+%token DNS
+%token SSL
+%token CERTFILE
+%token KEYFILE
+%token CAFILE
 /* and now a lot of privileges... */
 %token TPRIV_CHAN_LIMIT TPRIV_MODE_LCHAN TPRIV_DEOP_LCHAN TPRIV_WALK_LCHAN
 %token TPRIV_LOCAL_KILL TPRIV_REHASH TPRIV_RESTART TPRIV_DIE
 %token TPRIV_GLINE TPRIV_LOCAL_GLINE TPRIV_LOCAL_JUPE TPRIV_LOCAL_BADCHAN
 %token TPRIV_LOCAL_OPMODE TPRIV_OPMODE TPRIV_SET TPRIV_WHOX TPRIV_BADCHAN
 %token TPRIV_SEE_CHAN TPRIV_SHOW_INVIS TPRIV_SHOW_ALL_INVIS TPRIV_PROPAGATE
-%token TPRIV_UNLIMIT_QUERY TPRIV_DISPLAY TPRIV_SEE_OPERS TPRIV_WIDE_GLINE
-%token TPRIV_FORCE_OPMODE TPRIV_FORCE_LOCAL_OPMODE TPRIV_APASS_OPMODE
+%token TPRIV_UNLIMIT_QUERY TPRIV_UNLIMIT_FLOOD TPRIV_DISPLAY TPRIV_SEE_OPERS
+%token TPRIV_WIDE_GLINE TPRIV_FORCE_OPMODE TPRIV_FORCE_LOCAL_OPMODE
+%token TPRIV_APASS_OPMODE TPRIV_LIST_CHAN
 /* and some types... */
 %type <num> sizespec
 %type <num> timespec timefactor factoredtimes factoredtime
-%type <num> expr yesorno privtype
+%type <num> expr yesorno privtype address_family
 %left '+' '-'
 %left '*' '/'
 
@@ -180,7 +203,7 @@ static void parse_error(char *pattern,...) {
 %%
 /* Blocks in the config file... */
 blocks: blocks block | block;
-block: adminblock | generalblock | classblock | connectblock |
+block: adminblock | generalblock | classblock | connectblock | sslblock |
        uworldblock | operblock | portblock | jupeblock | clientblock |
        killblock | cruleblock | motdblock | featuresblock | quarantineblock |
        pseudoblock | iauthblock | error ';';
@@ -276,7 +299,9 @@ generalblock: GENERAL
     parse_error("Your General block must contain a numeric (between 1 and 4095).");
 };
 generalitems: generalitem generalitems | generalitem;
-generalitem: generalnumeric | generalname | generalvhost | generaldesc;
+generalitem: generalnumeric | generalname | generalvhost | generaldesc
+  | generaldnsvhost | generaldnsserver;
+
 generalnumeric: NUMERIC '=' NUMBER ';'
 {
   if (localConf.numeric == 0)
@@ -308,16 +333,48 @@ generaldesc: DESCRIPTION '=' QSTRING ';'
 generalvhost: VHOST '=' QSTRING ';'
 {
   struct irc_in_addr addr;
-  if (!strcmp($3, "*")) {
+  char *vhost = $3;
+
+  if (!strcmp(vhost, "*")) {
     /* This traditionally meant bind to all interfaces and connect
      * from the default. */
-  } else if (!ircd_aton(&addr, $3))
-    parse_error("Invalid virtual host '%s'.", $3);
+  } else if (!ircd_aton(&addr, vhost))
+    parse_error("Invalid virtual host '%s'.", vhost);
   else if (irc_in_addr_is_ipv4(&addr))
     memcpy(&VirtualHost_v4.addr, &addr, sizeof(addr));
   else
     memcpy(&VirtualHost_v6.addr, &addr, sizeof(addr));
-  MyFree($3);
+  MyFree(vhost);
+};
+
+generaldnsvhost: DNS VHOST '=' address_family QSTRING ';'
+{
+  struct irc_in_addr addr;
+  int families = $4;
+  char *vhost = $5;
+
+  if (!strcmp(vhost, "*")) {
+    /* Let the operating system assign the default. */
+  } else if (!ircd_aton(&addr, vhost))
+    parse_error("Invalid DNS virtual host '%s'.", vhost);
+  else
+  {
+    if ((families & USE_IPV4)
+        || (!families && irc_in_addr_is_ipv4(&addr)))
+      memcpy(&VirtualHost_dns_v4.addr, &addr, sizeof(addr));
+    if ((families & USE_IPV6)
+        || (!families && !irc_in_addr_is_ipv4(&addr)))
+      memcpy(&VirtualHost_dns_v6.addr, &addr, sizeof(addr));
+  }
+  MyFree(vhost);
+};
+
+generaldnsserver: DNS SERVER '=' QSTRING ';'
+{
+  char *server = $4;
+
+  add_nameserver(server);
+  MyFree(server);
 };
 
 adminblock: ADMIN
@@ -353,6 +410,40 @@ admincontact: CONTACT '=' QSTRING ';'
  localConf.contact = $3;
 };
 
+sslblock: SSL
+{
+  MyFree(localConf.sslcertfile);
+  MyFree(localConf.sslkeyfile);
+  MyFree(localConf.sslcafile);
+  localConf.sslcertfile = localConf.sslkeyfile = localConf.sslcafile = NULL;
+}
+'{' sslitems '}' ';'
+{
+  if (localConf.sslcertfile == NULL)
+    DupString(localConf.sslcertfile, "");
+  if (localConf.sslkeyfile == NULL)
+    DupString(localConf.sslkeyfile, "");
+  if (localConf.sslcafile == NULL)
+    DupString(localConf.sslcafile, "");
+};
+sslitems: sslitems sslitem | sslitem;
+sslitem: sslcertfile | sslkeyfile | sslcafile;
+sslcertfile: CERTFILE '=' QSTRING ';'
+{
+  MyFree(localConf.sslcertfile);
+  localConf.sslcertfile = $3;
+};
+sslkeyfile: KEYFILE '=' QSTRING ';'
+{
+  MyFree(localConf.sslkeyfile);
+  localConf.sslkeyfile = $3;
+};
+sslcafile: CAFILE '=' QSTRING ';'
+{
+  MyFree(localConf.sslcafile);
+  localConf.sslcafile = $3;
+};
+
 classblock: CLASS {
   tping = 90;
 } '{' classitems '}' ';'
@@ -410,7 +501,6 @@ classusermode: USERMODE '=' QSTRING ';'
 
 connectblock: CONNECT
 {
- maxlinks = 65535;
  flags = CONF_AUTOCONNECT;
 } '{' connectitems '}' ';'
 {
@@ -419,6 +509,8 @@ connectblock: CONNECT
   parse_error("Missing name in connect block");
  else if (pass == NULL)
   parse_error("Missing password in connect block");
+ else if (strlen(pass) > PASSWDLEN)
+  parse_error("Password too long in connect block");
  else if (host == NULL)
   parse_error("Missing host in connect block");
  else if (strchr(host, '*') || strchr(host, '?'))
@@ -433,7 +525,10 @@ connectblock: CONNECT
    aconf->conn_class = c_class;
    aconf->address.port = port;
    aconf->host = host;
-   aconf->maximum = maxlinks;
+   /* If the user specified a hub allowance, but not maximum links,
+    * allow an effectively unlimited number of hops.
+    */
+   aconf->maximum = (hub_limit != NULL && maxlinks == 0) ? 65535 : maxlinks;
    aconf->hub_limit = hub_limit;
    aconf->flags = flags;
    lookup_confhost(aconf);
@@ -447,7 +542,7 @@ connectblock: CONNECT
  }
  name = pass = host = origin = hub_limit = NULL;
  c_class = NULL;
- port = flags = 0;
+ port = flags = maxlinks = 0;
 };
 connectitems: connectitem connectitems | connectitem;
 connectitem: connectname | connectpass | connectclass | connecthost
@@ -516,32 +611,33 @@ uworldname: NAME '=' QSTRING ';'
 operblock: OPER '{' operitems '}' ';'
 {
   struct ConfItem *aconf = NULL;
+  struct SLink *link;
+
   if (name == NULL)
     parse_error("Missing name in operator block");
   else if (pass == NULL)
     parse_error("Missing password in operator block");
-  else if (host == NULL)
-    parse_error("Missing host in operator block");
+  /* Do not check password length because it may be crypted. */
+  else if (hosts == NULL)
+    parse_error("Missing host(s) in operator block");
   else if (c_class == NULL)
     parse_error("Invalid or missing class in operator block");
-  else {
+  else if (!FlagHas(&privs_dirty, PRIV_PROPAGATE)
+           && !FlagHas(&c_class->privs_dirty, PRIV_PROPAGATE))
+    parse_error("Operator block for %s and class %s have no LOCAL setting", name, c_class->cc_name);
+  else for (link = hosts; link != NULL; link = link->next) {
     aconf = make_conf(CONF_OPERATOR);
-    aconf->name = name;
-    aconf->passwd = pass;
-    conf_parse_userhost(aconf, host);
+    DupString(aconf->name, name);
+    DupString(aconf->passwd, pass);
+    conf_parse_userhost(aconf, link->value.cp);
     aconf->conn_class = c_class;
     memcpy(&aconf->privs, &privs, sizeof(aconf->privs));
     memcpy(&aconf->privs_dirty, &privs_dirty, sizeof(aconf->privs_dirty));
-    if (!FlagHas(&privs_dirty, PRIV_PROPAGATE)
-        && !FlagHas(&c_class->privs_dirty, PRIV_PROPAGATE))
-      parse_error("Operator block for %s and class %s have no LOCAL setting", name, c_class->cc_name);
-  }
-  if (!aconf) {
-    MyFree(name);
-    MyFree(pass);
-    MyFree(host);
   }
-  name = pass = host = NULL;
+  MyFree(name);
+  MyFree(pass);
+  free_slist(&hosts);
+  name = pass = NULL;
   c_class = NULL;
   memset(&privs, 0, sizeof(privs));
   memset(&privs_dirty, 0, sizeof(privs_dirty));
@@ -560,16 +656,19 @@ operpass: PASS '=' QSTRING ';'
 };
 operhost: HOST '=' QSTRING ';'
 {
- MyFree(host);
+ struct SLink *link;
+ link = make_link();
  if (!strchr($3, '@'))
  {
    int uh_len;
-   host = (char*) MyMalloc((uh_len = strlen($3)+3));
-   ircd_snprintf(0, host, uh_len, "*@%s", $3);
-   MyFree($3);
+   link->value.cp = (char*) MyMalloc((uh_len = strlen($3)+3));
+   ircd_snprintf(0, link->value.cp, uh_len, "*@%s", $3);
  }
  else
-   host = $3;
+   DupString(link->value.cp, $3);
+ MyFree($3);
+ link->next = hosts;
+ hosts = link;
 };
 operclass: CLASS '=' QSTRING ';'
 {
@@ -613,9 +712,11 @@ privtype: TPRIV_CHAN_LIMIT { $$ = PRIV_CHAN_LIMIT; } |
           TPRIV_SHOW_ALL_INVIS { $$ = PRIV_SHOW_ALL_INVIS; } |
           TPRIV_PROPAGATE { $$ = PRIV_PROPAGATE; } |
           TPRIV_UNLIMIT_QUERY { $$ = PRIV_UNLIMIT_QUERY; } |
+          TPRIV_UNLIMIT_FLOOD { $$ = PRIV_UNLIMIT_FLOOD; } |
           TPRIV_DISPLAY { $$ = PRIV_DISPLAY; } |
           TPRIV_SEE_OPERS { $$ = PRIV_SEE_OPERS; } |
           TPRIV_WIDE_GLINE { $$ = PRIV_WIDE_GLINE; } |
+          TPRIV_LIST_CHAN { $$ = PRIV_LIST_CHAN; } |
           LOCAL { $$ = PRIV_PROPAGATE; invert = 1; } |
           TPRIV_FORCE_OPMODE { $$ = PRIV_FORCE_OPMODE; } |
           TPRIV_FORCE_LOCAL_OPMODE { $$ = PRIV_FORCE_LOCAL_OPMODE; } |
@@ -623,29 +724,89 @@ privtype: TPRIV_CHAN_LIMIT { $$ = PRIV_CHAN_LIMIT; } |
 
 yesorno: YES { $$ = 1; } | NO { $$ = 0; };
 
+/* not a recursive definition because some pedant will just come along
+ * and whine that the parser accepts "ipv4 ipv4 ipv4 ipv4"
+ */
+address_family:
+               { $$ = 0; }
+    | TOK_IPV4 { $$ = USE_IPV4; }
+    | TOK_IPV6 { $$ = USE_IPV6; }
+    | TOK_IPV4 TOK_IPV6 { $$ = USE_IPV4 | USE_IPV6; }
+    | TOK_IPV6 TOK_IPV4 { $$ = USE_IPV6 | USE_IPV4; }
+    ;
+
 /* The port block... */
-portblock: PORT '{' portitems '}' ';'
-{
-  if (port > 0 && port <= 0xFFFF)
-    add_listener(port, host, pass, tconn, tping);
-  else
-    parse_error("Port %d is out of range", port);
-  MyFree(host);
+portblock: PORT '{' portitems '}' ';' {
+  struct ListenerFlags flags_here;
+  struct SLink *link;
+  if (hosts == NULL) {
+    struct SLink *link;
+    link = make_link();
+    DupString(link->value.cp, "*");
+    link->flags = 0;
+    link->next = hosts;
+    hosts = link;
+  }
+  for (link = hosts; link != NULL; link = link->next) {
+    memcpy(&flags_here, &listen_flags, sizeof(flags_here));
+    switch (link->flags & (USE_IPV4 | USE_IPV6)) {
+    case USE_IPV4:
+      FlagSet(&flags_here, LISTEN_IPV4);
+      break;
+    case USE_IPV6:
+      FlagSet(&flags_here, LISTEN_IPV6);
+      break;
+    default: /* 0 or USE_IPV4|USE_IPV6 */
+      FlagSet(&flags_here, LISTEN_IPV4);
+      FlagSet(&flags_here, LISTEN_IPV6);
+      break;
+    }
+    
+    if (link->flags & 65535)
+      port = link->flags & 65535;
+    add_listener(port, link->value.cp, pass, &flags_here);
+  }
+  free_slist(&hosts);
   MyFree(pass);
-  host = pass = NULL;
-  port = tconn = tping = 0;
+  memset(&listen_flags, 0, sizeof(listen_flags));
+  pass = NULL;
+  port = 0;
 };
 portitems: portitem portitems | portitem;
-portitem: portnumber | portvhost | portmask | portserver | porthidden;
-portnumber: PORT '=' NUMBER ';'
+portitem: portnumber | portvhost | portvhostnumber | portmask | portserver | portssl | porthidden;
+portnumber: PORT '=' address_family NUMBER ';'
 {
-  port = $3;
+  if ($4 < 1 || $4 > 65535) {
+    parse_error("Port %d is out of range", port);
+  } else {
+    port = $3 | $4;
+    if (hosts && (0 == (hosts->flags & 65535)))
+      hosts->flags = (hosts->flags & ~65535) | port;
+  }
 };
 
-portvhost: VHOST '=' QSTRING ';'
+portvhost: VHOST '=' address_family QSTRING ';'
 {
-  MyFree(host);
-  host = $3;
+  struct SLink *link;
+  link = make_link();
+  link->value.cp = $4;
+  link->flags = $3 | port;
+  link->next = hosts;
+  hosts = link;
+};
+
+portvhostnumber: VHOST '=' address_family QSTRING NUMBER ';'
+{
+  if ($5 < 1 || $5 > 65535) {
+    parse_error("Port %d is out of range", port);
+  } else {
+    struct SLink *link;
+    link = make_link();
+    link->value.cp = $4;
+    link->flags = $3 | $5;
+    link->next = hosts;
+    hosts = link;
+  }
 };
 
 portmask: MASK '=' QSTRING ';'
@@ -656,18 +817,26 @@ portmask: MASK '=' QSTRING ';'
 
 portserver: SERVER '=' YES ';'
 {
-  tconn = -1;
+  FlagSet(&listen_flags, LISTEN_SERVER);
 } | SERVER '=' NO ';'
 {
-  tconn = 0;
+  FlagClr(&listen_flags, LISTEN_SERVER);
+};
+
+portssl: SSL '=' YES ';'
+{
+  FlagSet(&listen_flags, LISTEN_SSL);
+} | SSL '=' NO ';'
+{
+  FlagClr(&listen_flags, LISTEN_SSL);
 };
 
 porthidden: HIDDEN '=' YES ';'
 {
-  tping = -1;
+  FlagSet(&listen_flags, LISTEN_HIDDEN);
 } | HIDDEN '=' NO ';'
 {
-  tping = 0;
+  FlagClr(&listen_flags, LISTEN_HIDDEN);
 };
 
 clientblock: CLIENT
@@ -683,6 +852,8 @@ clientblock: CLIENT
 
   if (!c_class)
     parse_error("Invalid or missing class in Client block");
+  else if (pass && strlen(pass) > PASSWDLEN)
+    parse_error("Password too long in connect block");
   else if (ip && !ipmask_parse(ip, &addr, &addrbits))
     parse_error("Invalid IP address %s in Client block", ip);
   else {
@@ -709,6 +880,7 @@ clientblock: CLIENT
   host = NULL;
   username = NULL;
   c_class = NULL;
+  maxlinks = 0;
   ip = NULL;
   pass = NULL;
   port = 0;
@@ -841,28 +1013,30 @@ cruleblock: CRULE
 } '{' cruleitems '}' ';'
 {
   struct CRuleNode *node = NULL;
-  if (host == NULL)
-    parse_error("Missing host in crule block");
+  struct SLink *link;
+
+  if (hosts == NULL)
+    parse_error("Missing server(s) in crule block");
   else if (pass == NULL)
     parse_error("Missing rule in crule block");
   else if ((node = crule_parse(pass)) == NULL)
     parse_error("Invalid rule '%s' in crule block", pass);
-  else
+  else for (link = hosts; link != NULL; link = link->next)
   {
     struct CRuleConf *p = (struct CRuleConf*) MyMalloc(sizeof(*p));
-    p->hostmask = host;
-    p->rule = pass;
+    if (node == NULL)
+      node = crule_parse(pass);
+    DupString(p->hostmask, link->value.cp);
+    DupString(p->rule, pass);
     p->type = tconn;
     p->node = node;
+    node = NULL;
     p->next = cruleConfList;
     cruleConfList = p;
   }
-  if (!node)
-  {
-    MyFree(host);
-    MyFree(pass);
-  }
-  host = pass = NULL;
+  free_slist(&hosts);
+  MyFree(pass);
+  pass = NULL;
   tconn = 0;
 };
 
@@ -871,9 +1045,11 @@ cruleitem: cruleserver | crulerule | cruleall;
 
 cruleserver: SERVER '=' QSTRING ';'
 {
-  MyFree(host);
-  collapse($3);
-  host = $3;
+  struct SLink *link;
+  link = make_link();
+  link->value.cp = $3;
+  link->next = hosts;
+  hosts = link;
 };
 
 crulerule: RULE '=' QSTRING ';'
@@ -892,22 +1068,29 @@ cruleall: ALL '=' YES ';'
 
 motdblock: MOTD '{' motditems '}' ';'
 {
-  if (host != NULL && pass != NULL)
-    motd_add(host, pass);
-  MyFree(host);
+  struct SLink *link;
+  if (pass != NULL)
+    for (link = hosts; link != NULL; link = link->next)
+      motd_add(link->value.cp, pass);
+  free_slist(&hosts);
   MyFree(pass);
-  host = pass = NULL;
+  pass = NULL;
 };
 
 motditems: motditem motditems | motditem;
 motditem: motdhost | motdfile;
 motdhost: HOST '=' QSTRING ';'
 {
-  host = $3;
+  struct SLink *link;
+  link = make_link();
+  link->value.cp = $3;
+  link->next = hosts;
+  hosts = link;
 };
 
 motdfile: TFILE '=' QSTRING ';'
 {
+  MyFree(pass);
   pass = $3;
 };
 
@@ -958,6 +1141,8 @@ pseudoitems '}' ';'
     parse_error("Missing name in pseudo %s block", smap->command);
   else if (!smap->services)
     parse_error("Missing nick in pseudo %s block", smap->command);
+  else if (!strIsAlpha(smap->command))
+    parse_error("Pseudo command %s invalid: must all be letters", smap->command);
   else
     valid = 1;
   if (valid && register_mapping(smap))
@@ -1007,7 +1192,10 @@ iauthblock: IAUTH '{' iauthitems '}' ';'
 {
   auth_spawn(stringno, stringlist);
   while (stringno > 0)
-    MyFree(stringlist[stringno--]);
+  {
+    --stringno;
+    MyFree(stringlist[stringno]);
+  }
 };
 
 iauthitems: iauthitem iauthitems | iauthitem;
@@ -1015,5 +1203,8 @@ iauthitem: iauthprogram;
 iauthprogram: PROGRAM '='
 {
   while (stringno > 0)
-    MyFree(stringlist[stringno--]);
+  {
+    --stringno;
+    MyFree(stringlist[stringno]);
+  }
 } stringlist ';';