]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3477] Checkpoint before regen
authorFrancis Dupont <fdupont@isc.org>
Thu, 4 Jul 2024 15:17:31 +0000 (17:17 +0200)
committerFrancis Dupont <fdupont@isc.org>
Thu, 1 Aug 2024 07:23:53 +0000 (09:23 +0200)
doc/examples/ddns/all-keys.json
doc/examples/ddns/comments.json
doc/examples/kea4/all-keys.json
doc/examples/kea6/all-keys.json
doc/examples/kea6/comments.json
src/bin/d2/d2_lexer.ll
src/bin/d2/d2_parser.yy

index bcb4735d9868b2772d2b5c472768e8cdcd52d1fe..c2930e4774232c488d0b3f27857b2d4cba79a919 100644 (file)
     "ncr-format": "JSON",
 
     // Command control socket configuration parameters for Kea DHCP-DDNS server.
-    "control-socket": {
+    "control-sockets": [
+        {
+            // Control socket type used by the Kea DHCP-DDNS server.
+            // Must be unix, http or https.
+            "socket-type": "unix",
+
+            // Location of the UNIX domain socket file the DHCP-DDNS
+            // server uses to receive control commands from the
+            // local server administrator.
+            "socket-name": "/tmp/kea-ddns-ctrl-socket"
+        },
+        {
+            // Control socket type used by the Kea DHCP-DDNS server.
+            // Must be unix, http or https.
+            "socket-type": "https",
+
+            // Address of the HTTPS socket the Kea DHCP-DDNS server should
+            // listen for incoming queries. In fact an alias of
+            // socket-name.
+            "socket-address": "127.0.0.1",
+
+            // Port of the HTTPS socket the Kea DHCP-DDNS server
+            // should listen for incoming queries.
+            "socket-port": 8053,
+
+            // TLS trust anchor (Certificate Authority). This is a
+            // file name or a directory path. Make sense with other
+            // TLS parameters only for the https control socket type.
+            "trust-anchor": "my-ca",
+
+            // TLS server certificate file name.
+            "cert-file": "my-cert",
+
+            // TLS server private key file name.
+            "key-file": "my-key",
+
+            // TLS require client certificates flag. Default is
+            // true and means require client certificates. False
+            // means they are optional.
+            "cert-required": true,
+
+            // Optional authentication.
+            "authentication": {
+                // Required authentication type. The only supported
+                // value is basic for the basic HTTP authentication.
+                "type": "basic",
+
+                // An optional parameter is the basic HTTP
+                // authentication realm.  Its default is
+                // "kea-dhcp-ddns-server"
+                "realm": "kea-dhcp-ddns-server",
+
+                // This optional parameter can be used to specify a common
+                // prefix for files handling client credentials.
+                "directory": "/tmp/kea-creds",
+
+                // This list specifies the user ids and passwords to
+                // use for basic HTTP authentication. If empty or not
+                // present any client is authorized.
+                "clients": [
+                    // This specifies an authorized client.
+                    {
+                        // The user id must not be empty or contain
+                        // the ':' character. It is a mandatoryparameter.
+                        "user": "admin",
 
-        // Location of the UNIX domain socket file the DHCP-DDNS server uses
-        // to receive control commands from the Kea Control Agent or the
-        // local server administrator.
-        "socket-name": "/tmp/kea-ddns-ctrl-socket",
+                        // If password is not specified an empty
+                        // password is used.
+                        "password": "1234"
+                    },
 
-        // Control socket type used by the Kea DHCP-DDNS server.
-        // The 'unix' socket is currently the only supported type.
-        "socket-type": "unix"
-    },
+                    // This specifies a hidden client.
+                    {
+                        // The user id is the content of the
+                        // file /tmp/kea-creds/hiddenu.
+                        "user-file": "hiddenu",
+
+                        // The password is the content of the
+                        // file /tmp/kea-creds/hiddenp.
+                        "password-file": "hiddenp"
+                    },
+
+                    // This specifies a hidden client using a secret
+                    // in a file.
+                    {
+                        // The secret is the content of the file
+                        // /tmp/kea-creds/hiddens which must be in
+                        // the <user-id>:<password> format.
+                        "password-file": "hiddens"
+                    }
+                ]
+            }
+        }
+    ],
 
     // List of hook libraries and their specific configuration parameters
     // to be loaded by Kea DHCP-DDNS server.
index a7717d17c83026d05a3bd2a0d37793673c1bcbf2..ecd68c47653881f9db85a0bc0628149555c6e83c 100644 (file)
     "port": 53001,
     "dns-server-timeout" : 1000,
 
-    "control-socket":
-    {
-        "comment": "Control channel",
-        "socket-type": "unix",
-        "socket-name": "/tmp/kea-ddns-ctrl-socket"
-    },
+    // In control sockets.
+    "control-sockets": [
+        {
+            "socket-type": "unix",
+            "socket-name": "/tmp/kea-ddns-ctrl-socket",
+            "user-context": { "comment": "Indirect comment" }
+        },
+        {
+            "comment": "HTTP control socket",
+            "socket-type": "http",
+            "socket-address": "::1",
+            "socket-port": 8053,
+            // In authentication
+            "authentication": {
+                "comment": "basic HTTP authentication",
+                "type": "basic",
+                // In basic HTTP authentication clients
+                "clients": [ {
+                    "comment": "admin is authorized",
+                    "user": "admin",
+                    "password": "1234"
+                } ]
+            }
+        }
+    ],
 
     "forward-ddns":
     {
index fe1105baf7646a6861f26c355bbdfebd67b2bc4a..c23b05466efe2fd7bbe2677672291a3cb34ea196 100644 (file)
             }
         ],
 
-
         // Specifies a prefix to be prepended to the generated Client FQDN.
         // It may be specified at the global, shared-network, and subnet levels.
         "ddns-generated-prefix": "myhost",
index 5fbea90be476f1b05ccf5334e3f6ddd2bfd05f41..2fb754311c243b7db295bc1cbc65da820548184a 100644 (file)
             "lenient-option-parsing": true
         },
 
-        // Command control socket configuration parameters for the Kea DHCPv4 server.
+        // Command control socket configuration parameters for the Kea DHCPv6 server.
         "control-sockets": [
             {
-                // Control socket type used by the Kea DHCPv4 server.
+                // Control socket type used by the Kea DHCPv6 server.
                 // Must be unix, http or https.
                 "socket-type": "unix",
 
-               // Location of the UNIX domain socket file the DHCPv4
+               // Location of the UNIX domain socket file the DHCPv6
                // server uses to receive control commands from the
                // local server administrator.
-               "socket-name": "/tmp/kea4-ctrl-socket"
+               "socket-name": "/tmp/kea6-ctrl-socket"
             },
             {
-                // Control socket type used by the Kea DHCPv4 server.
+                // Control socket type used by the Kea DHCPv6 server.
                 // Must be unix, http or https.
                 "socket-type": "https",
 
-                // Address of the HTTPS socket the Kea DHCPv4 server should
+                // Address of the HTTPS socket the Kea DHCPv6 server should
                 // listen for incoming queries. In fact an alias of
                 // socket-name.
                 "socket-address": "::1",
 
-                // Port of the HTTPS socket the Kea DHCPv4 server
+                // Port of the HTTPS socket the Kea DHCPv6 server
                 // should listen for incoming queries. If enabling HA
                 // and multi-threading, the 8000 port is used by the
                 // HA hook library http listener. When using HA hook
 
                     // An optional parameter is the basic HTTP
                     // authentication realm.  Its default is
-                    // "kea-dhcpv4-server"
-                    "realm": "kea-dhcpv4-server",
+                    // "kea-dhcpv6-server"
+                    "realm": "kea-dhcpv6-server",
 
                     // This optional parameter can be used to specify a common
                     // prefix for files handling client credentials.
             }
         ],
 
-
         // Specifies a prefix to be prepended to the generated Client FQDN.
         // It may be specified at the global, shared-network, and subnet levels.
         "ddns-generated-prefix": "myhost",
index dbc48072ee9eeb20a0388645d8699b9f21237fed..6da240e34d8ba153e5c12f4b02d7989bcb40ba17 100644 (file)
@@ -55,7 +55,7 @@
    "control-sockets": [
        {
            "socket-type": "unix",
-           "socket-name": "/tmp/kea4-ctrl-socket",
+           "socket-name": "/tmp/kea6-ctrl-socket",
            "user-context": { "comment": "Indirect comment" }
        },
        {
index be169a904fa0e2e235b18ac7ff18db4558900413..db036352ce9cef7f38c3b62a514599e2b70d3f91 100644 (file)
@@ -274,6 +274,8 @@ ControlCharacterFill            [^"\\]|\\["\\/bfnrtu]
     case isc::d2::D2ParserContext::TSIG_KEY:
     case isc::d2::D2ParserContext::TSIG_KEYS:
     case isc::d2::D2ParserContext::CONTROL_SOCKET:
+    case isc::d2::D2ParserContext::AUTHENTICATION:
+    case isc::d2::D2ParserContext::CLIENTS:
     case isc::d2::D2ParserContext::LOGGERS:
         return isc::d2::D2Parser::make_USER_CONTEXT(driver.loc_);
     default:
@@ -291,6 +293,8 @@ ControlCharacterFill            [^"\\]|\\["\\/bfnrtu]
     case isc::d2::D2ParserContext::TSIG_KEY:
     case isc::d2::D2ParserContext::TSIG_KEYS:
     case isc::d2::D2ParserContext::CONTROL_SOCKET:
+    case isc::d2::D2ParserContext::AUTHENTICATION:
+    case isc::d2::D2ParserContext::CLIENTS:
     case isc::d2::D2ParserContext::LOGGERS:
         return isc::d2::D2Parser::make_COMMENT(driver.loc_);
     default:
@@ -417,6 +421,15 @@ ControlCharacterFill            [^"\\]|\\["\\/bfnrtu]
     }
 }
 
+\"control-sockets\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::DHCPDDNS:
+        return isc::d2::D2Parser::make_CONTROL_SOCKET(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("control-socket", driver.loc_);
+    }
+}
+
 \"socket-type\" {
     switch(driver.ctx_) {
     case isc::d2::D2ParserContext::CONTROL_SOCKET:
@@ -426,6 +439,33 @@ ControlCharacterFill            [^"\\]|\\["\\/bfnrtu]
     }
 }
 
+\"unix\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::CONTROL_SOCKET_TYPE:
+        return isc::d2::D2Parser::make_UNIX(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("unix", driver.loc_);
+    }
+}
+
+\"http\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::CONTROL_SOCKET_TYPE:
+        return isc::d2::D2Parser::make_HTTP(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("http", driver.loc_);
+    }
+}
+
+\"https\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::CONTROL_SOCKET_TYPE:
+        return isc::d2::D2Parser::make_HTTPS(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("https", driver.loc_);
+    }
+}
+
 \"socket-name\" {
     switch(driver.ctx_) {
     case isc::d2::D2ParserContext::CONTROL_SOCKET:
@@ -435,6 +475,150 @@ ControlCharacterFill            [^"\\]|\\["\\/bfnrtu]
     }
 }
 
+\"socket-address\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::CONTROL_SOCKET:
+        return isc::d2::D2Parser::make_SOCKET_ADDRESS(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("socket-address", driver.loc_);
+    }
+}
+
+\"socket-port\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::CONTROL_SOCKET:
+        return isc::d2::D2Parser::make_SOCKET_PORT(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("socket-port", driver.loc_);
+    }
+}
+
+\"authentication\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+        return isc::d2::D2Parser::make_AUTHENTICATION(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("authentication", driver.loc_);
+    }
+}
+
+\"type\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::AUTHENTICATION:
+        return isc::d2::D2Parser::make_TYPE(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("type", driver.loc_);
+    }
+}
+
+\"basic\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::AUTH_TYPE:
+        return isc::d2::D2Parser::make_BASIC(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("basic", driver.loc_);
+    }
+}
+
+\"realm\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::AUTHENTICATION:
+        return isc::d2::D2Parser::make_REALM(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("realm", driver.loc_);
+    }
+}
+
+\"directory\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::AUTHENTICATION:
+        return isc::d2::D2Parser::make_DIRECTORY(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("directory", driver.loc_);
+    }
+}
+
+\"clients\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::AUTHENTICATION:
+        return isc::d2::D2Parser::make_CLIENTS(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("clients", driver.loc_);
+    }
+}
+
+\"user\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CLIENTS:
+        return isc::d2::D2Parser::make_USER(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("user", driver.loc_);
+    }
+}
+
+\"user-file\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CLIENTS:
+        return isc::d2::D2Parser::make_USER_FILE(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("user-file", driver.loc_);
+    }
+}
+
+\"password\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CLIENTS:
+        return isc::d2::D2Parser::make_PASSWORD(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("password", driver.loc_);
+    }
+}
+
+\"password-file\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CLIENTS:
+        return isc::d2::D2Parser::make_PASSWORD_FILE(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("password-file", driver.loc_);
+    }
+}
+
+\"trust-anchor\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+        return isc::d2::D2Parser::make_TRUST_ANCHOR(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("trust-anchor", driver.loc_);
+    }
+}
+
+\"cert-file\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+        return isc::d2::D2Parser::make_CERT_FILE(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("cert-file", driver.loc_);
+    }
+}
+
+\"key-file\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+        return isc::d2::D2Parser::make_KEY_FILE(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("key-file", driver.loc_);
+    }
+}
+
+\"cert-required\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+        return isc::d2::D2Parser::make_CERT_REQUIRED(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("cert-required", driver.loc_);
+    }
+}
+
 \"hooks-libraries\" {
     switch(driver.ctx_) {
     case isc::d2::D2ParserContext::DHCPDDNS:
index f42f498ab97002b81815f5682e173c13a325c35a..f694cfd5c2727d2b686f9d4535142bf87654b389 100644 (file)
@@ -78,8 +78,22 @@ using namespace std;
   SECRET_FILE "secret-file"
 
   CONTROL_SOCKET "control-socket"
+  CONTROL_SOCKETS "control-sockets"
   SOCKET_TYPE "socket-type"
+  UNIX "unix"
+  HTTP "http"
+  HTTPS "https"
   SOCKET_NAME "socket-name"
+  SOCKET_ADDRESS "socket-address"
+  SOCKET_PORT "socket-port"
+  AUTHENTICATION "authentication"
+  BASIC "basic"
+  REALM "realm"
+  DIRECTORY "directory"
+  CLIENTS "clients"
+  USER_FILE "user-file"
+  PASSWORD_FILE "password-file"
+  CERT_REQUIRED "cert-required"
 
   HOOKS_LIBRARIES "hooks-libraries"
   LIBRARY "library"
@@ -118,6 +132,8 @@ using namespace std;
 %type <ElementPtr> value
 %type <ElementPtr> map_value
 %type <ElementPtr> ncr_protocol_value
+%type <ElementPtr> control_socket_type_value
+%type <ElementPtr> auth_type_value
 
 %printer { yyoutput << $$; } <*>;
 
@@ -286,6 +302,7 @@ dhcpddns_param: ip_address
               | reverse_ddns
               | tsig_keys
               | control_socket
+              | control_sockets
               | hooks_libraries
               | loggers
               | user_context
@@ -739,10 +756,11 @@ tsig_key_secret_file: SECRET_FILE {
 
 // --- end of tsig-keys ---------------------------------
 
-// --- control socket ----------------------------------------
+// --- control sockets ----------------------------------------
 
 control_socket: CONTROL_SOCKET {
     ctx.unique("control-socket", ctx.loc2pos(@1));
+    ctx.unique("control-sockets", ctx.loc2pos(@1));
     ElementPtr m(new MapElement(ctx.loc2pos(@1)));
     ctx.stack_.back()->set("control-socket", m);
     ctx.stack_.push_back(m);
@@ -752,6 +770,37 @@ control_socket: CONTROL_SOCKET {
     ctx.leave();
 };
 
+control_sockets: CONTROL_SOCKETS {
+    ctx.unique("control-sockets", ctx.loc2pos(@1));
+    ctx.unique("control-socket", ctx.loc2pos(@1));
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("control-sockets", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.CONTROL_SOCKET);
+} COLON LSQUARE_BRACKET control_socket_list RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+control_socket_list: %empty
+                   | not_empty_control_socket_list
+                   ;
+
+not_empty_control_socket_list: control_socket_entry
+                             | not_empty_control_socket_list COMMA control_socket_entry
+                             | not_empty_control_socket_list COMMA {
+                                 ctx.warnAboutExtraCommas(@2);
+                                 }
+                             ;
+
+control_socket_entry: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} control_socket_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
 control_socket_params: control_socket_param
                      | control_socket_params COMMA control_socket_param
                      | control_socket_params COMMA {
@@ -761,6 +810,13 @@ control_socket_params: control_socket_param
 
 control_socket_param: control_socket_type
                     | control_socket_name
+                    | control_socket_address
+                    | control_socket_port
+                    | authentication
+                    | trust_anchor
+                    | cert_file
+                    | key_file
+                    | cert_required
                     | user_context
                     | comment
                     | unknown_map_entry
@@ -768,13 +824,18 @@ control_socket_param: control_socket_type
 
 control_socket_type: SOCKET_TYPE {
     ctx.unique("socket-type", ctx.loc2pos(@1));
-    ctx.enter(ctx.NO_KEYWORD);
-} COLON STRING {
-    ElementPtr stype(new StringElement($4, ctx.loc2pos(@4)));
-    ctx.stack_.back()->set("socket-type", stype);
+    ctx.enter(ctx.CONTROL_SOCKET_TYPE);
+} COLON control_socket_type_value {
+    ctx.stack_.back()->set("socket-type", $4);
     ctx.leave();
 };
 
+control_socket_type_value:
+    UNIX { $$ = ElementPtr(new StringElement("unix", ctx.loc2pos(@1))); }
+  | HTTP { $$ = ElementPtr(new StringElement("http", ctx.loc2pos(@1))); }
+  | HTTPS { $$ = ElementPtr(new StringElement("https", ctx.loc2pos(@1))); }
+  ;
+
 control_socket_name: SOCKET_NAME {
     ctx.unique("socket-name", ctx.loc2pos(@1));
     ctx.enter(ctx.NO_KEYWORD);
@@ -784,6 +845,151 @@ control_socket_name: SOCKET_NAME {
     ctx.leave();
 };
 
+control_socket_address: SOCKET_ADDRESS {
+    ctx.unique("socket-address", ctx.loc2pos(@1));
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr address(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("socket-address", address);
+    ctx.leave();
+};
+
+control_socket_port: SOCKET_PORT COLON INTEGER {
+    ctx.unique("socket-port", ctx.loc2pos(@1));
+    ElementPtr port(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("socket-port", port);
+};
+
+cert_required: CERT_REQUIRED COLON BOOLEAN {
+    ctx.unique("cert-required", ctx.loc2pos(@1));
+    ElementPtr req(new BoolElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("cert-required", req);
+};
+
+// --- authentication ---------------------------------------------
+
+authentication: AUTHENTICATION {
+    ctx.unique("authentication", ctx.loc2pos(@1));
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("authentication", m);
+    ctx.stack_.push_back(m);
+    ctx.enter(ctx.AUTHENTICATION);
+} COLON LCURLY_BRACKET auth_params RCURLY_BRACKET {
+    // The type parameter is required
+    ctx.require("type", ctx.loc2pos(@4), ctx.loc2pos(@6));
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+auth_params: auth_param
+           | auth_params COMMA auth_param
+           | auth_params COMMA {
+               ctx.warnAboutExtraCommas(@2);
+               }
+           ;
+
+auth_param: auth_type
+          | realm
+          | directory
+          | clients
+          | comment
+          | user_context
+          | unknown_map_entry
+          ;
+
+auth_type: TYPE {
+    ctx.unique("type", ctx.loc2pos(@1));
+    ctx.enter(ctx.AUTH_TYPE);
+} COLON auth_type_value {
+    ctx.stack_.back()->set("type", $4);
+    ctx.leave();
+};
+
+auth_type_value: BASIC { $$ = ElementPtr(new StringElement("basic", ctx.loc2pos(@1))); }
+               ;
+
+realm: REALM {
+    ctx.unique("realm", ctx.loc2pos(@1));
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr realm(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("realm", realm);
+    ctx.leave();
+};
+
+directory: DIRECTORY {
+    ctx.unique("directory", ctx.loc2pos(@1));
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr directory(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("directory", directory);
+    ctx.leave();
+};
+
+clients: CLIENTS {
+    ctx.unique("clients", ctx.loc2pos(@1));
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("clients", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.CLIENTS);
+} COLON LSQUARE_BRACKET clients_list RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+clients_list: %empty
+            | not_empty_clients_list
+            ;
+
+not_empty_clients_list: basic_auth
+                      | not_empty_clients_list COMMA basic_auth
+                      | not_empty_clients_list COMMA {
+                          ctx.warnAboutExtraCommas(@2);
+                          }
+                      ;
+
+basic_auth: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} clients_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+clients_params: clients_param
+              | clients_params COMMA clients_param
+              | clients_params COMMA {
+                  ctx.warnAboutExtraCommas(@2);
+                  }
+              ;
+
+clients_param: user
+             | user_file
+             | password
+             | password_file
+             | user_context
+             | comment
+             | unknown_map_entry
+             ;
+
+user_file: USER_FILE {
+    ctx.unique("user-file", ctx.loc2pos(@1));
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr user(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("user-file", user);
+    ctx.leave();
+};
+
+password_file: PASSWORD_FILE {
+    ctx.unique("password-file", ctx.loc2pos(@1));
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr password(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("password-file", password);
+    ctx.leave();
+};
+
 // --- hooks libraries -----------------------------------------
 
 hooks_libraries: HOOKS_LIBRARIES {