]> granicus.if.org Git - icinga2/commitdiff
Implement the "pki request" and "pki ticket" commands
authorGunnar Beutner <gunnar.beutner@netways.de>
Thu, 16 Oct 2014 10:27:09 +0000 (12:27 +0200)
committerGunnar Beutner <gunnar.beutner@netways.de>
Thu, 16 Oct 2014 10:27:34 +0000 (12:27 +0200)
refs #7244

13 files changed:
etc/icinga2/features-available/api.conf
lib/base/application.cpp
lib/base/scriptvariable.cpp
lib/base/scriptvariable.hpp
lib/base/tlsstream.cpp
lib/base/tlsutility.cpp
lib/base/tlsutility.hpp
lib/cli/CMakeLists.txt
lib/cli/pkisigncsrcommand.cpp
lib/remote/apiclient.cpp
lib/remote/apiclient.hpp
lib/remote/apilistener.ti
lib/remote/remote-type.conf

index 104d82c75aabe1a3be1725891abd7a7e75f3088d..44a43f3fd0e04cdf3bcb62f237196db4c96b6767 100644 (file)
@@ -6,4 +6,6 @@ object ApiListener "api" {
   cert_path = SysconfDir + "/icinga2/pki/" + NodeName + ".crt"
   key_path = SysconfDir + "/icinga2/pki/" + NodeName + ".key"
   ca_path = SysconfDir + "/icinga2/pki/ca.crt"
+
+  //ticket_salt = "<secret key>"
 }
index 053844dc06391e490377b9750d2aaba99e6fe898..acb6d751a17c3bc93992297dcf1f5167e837015d 100644 (file)
@@ -918,7 +918,7 @@ void Application::DeclareLocalStateDir(const String& path)
  */
 String Application::GetZonesDir(void)
 {
-       return ScriptVariable::Get("ZonesDir");
+       return ScriptVariable::Get("ZonesDir", &Empty);
 }
 
 /**
@@ -938,7 +938,8 @@ void Application::DeclareZonesDir(const String& path)
  */
 String Application::GetPkgDataDir(void)
 {
-       return ScriptVariable::Get("PkgDataDir");
+       String defaultValue = "";
+       return ScriptVariable::Get("PkgDataDir", &Empty);
 }
 
 /**
@@ -958,7 +959,7 @@ void Application::DeclarePkgDataDir(const String& path)
  */
 String Application::GetIncludeConfDir(void)
 {
-       return ScriptVariable::Get("IncludeConfDir");
+       return ScriptVariable::Get("IncludeConfDir", &Empty);
 }
 
 /**
@@ -978,7 +979,7 @@ void Application::DeclareIncludeConfDir(const String& path)
  */
 String Application::GetStatePath(void)
 {
-       return ScriptVariable::Get("StatePath");
+       return ScriptVariable::Get("StatePath", &Empty);
 }
 
 /**
@@ -998,7 +999,7 @@ void Application::DeclareStatePath(const String& path)
  */
 String Application::GetObjectsPath(void)
 {
-       return ScriptVariable::Get("ObjectsPath");
+       return ScriptVariable::Get("ObjectsPath", &Empty);
 }
 
 /**
@@ -1018,7 +1019,7 @@ void Application::DeclareObjectsPath(const String& path)
  */
 String Application::GetPidPath(void)
 {
-       return ScriptVariable::Get("PidPath");
+       return ScriptVariable::Get("PidPath", &Empty);
 }
 
 /**
index 55f0366dd23d255bb6a946d9d7328b6ede9270b6..5174ad5eb54ec49e347870015f45cd8b4e244cdd 100644 (file)
@@ -51,12 +51,16 @@ Value ScriptVariable::GetData(void) const
        return m_Data;
 }
 
-Value ScriptVariable::Get(const String& name)
+Value ScriptVariable::Get(const String& name, const Value *defaultValue)
 {
        ScriptVariable::Ptr sv = GetByName(name);
 
-       if (!sv)
+       if (!sv) {
+               if (defaultValue)
+                       return *defaultValue;
+
                BOOST_THROW_EXCEPTION(std::invalid_argument("Tried to access undefined script variable '" + name + "'"));
+       }
 
        return sv->GetData();
 }
index 5112afca99c229676702580fac801f33c7c7d29d..491e8b05a90c948956dd07a7acacbb5185d9cd38 100644 (file)
@@ -48,7 +48,7 @@ public:
        static ScriptVariable::Ptr GetByName(const String& name);
        static void Unregister(const String& name);
 
-       static Value Get(const String& name);
+       static Value Get(const String& name, const Value *defaultValue = NULL);
        static ScriptVariable::Ptr Set(const String& name, const Value& value, bool overwrite = true, bool make_const = false);
 
 private:
index b70e18cd63b00e19f896a75c04c84c1569eeb33d..347dd67c7673cf3c03126e189c56499fc65523e3 100644 (file)
@@ -161,7 +161,15 @@ size_t TlsStream::Read(void *buffer, size_t count)
        std::ostringstream msgbuf;
        char errbuf[120];
 
-       m_Socket->Poll(true, false);
+       bool want_read;
+
+       {
+               boost::mutex::scoped_lock lock(m_SSLLock);
+               want_read = SSL_want_read(m_SSL.get());
+       }
+
+       if (want_read)
+               m_Socket->Poll(true, false);
 
        boost::mutex::scoped_lock alock(m_IOActionLock);
 
@@ -213,7 +221,15 @@ void TlsStream::Write(const void *buffer, size_t count)
        std::ostringstream msgbuf;
        char errbuf[120];
 
-       m_Socket->Poll(false, true);
+       bool want_write;
+
+       {
+               boost::mutex::scoped_lock lock(m_SSLLock);
+               want_write = SSL_want_write(m_SSL.get());
+       }
+
+       if (want_write)
+               m_Socket->Poll(false, true);
 
        boost::mutex::scoped_lock alock(m_IOActionLock);
 
index 4c1000495d35a51a332a5abbd5dbd0696781b952..6d8262cdbc39bc054c0c723e6a0f828d4fa1dc69 100644 (file)
@@ -21,6 +21,7 @@
 #include "base/convert.hpp"
 #include "base/logger_fwd.hpp"
 #include "base/context.hpp"
+#include "base/application.hpp"
 
 namespace icinga
 {
@@ -110,29 +111,31 @@ shared_ptr<SSL_CTX> MakeSSLContext(const String& pubkey, const String& privkey,
                    << errinfo_openssl_error(ERR_peek_error()));
        }
 
-       if (!SSL_CTX_load_verify_locations(sslContext.get(), cakey.CStr(), NULL)) {
-               msgbuf << "Error loading and verifying locations in ca key file '" << cakey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
-               Log(LogCritical, "SSL", msgbuf.str());
-               BOOST_THROW_EXCEPTION(openssl_error()
-                   << boost::errinfo_api_function("SSL_CTX_load_verify_locations")
-                   << errinfo_openssl_error(ERR_peek_error())
-                   << boost::errinfo_file_name(cakey));
+       if (!cakey.IsEmpty()) {
+               if (!SSL_CTX_load_verify_locations(sslContext.get(), cakey.CStr(), NULL)) {
+                       msgbuf << "Error loading and verifying locations in ca key file '" << cakey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
+                       Log(LogCritical, "SSL", msgbuf.str());
+                       BOOST_THROW_EXCEPTION(openssl_error()
+                           << boost::errinfo_api_function("SSL_CTX_load_verify_locations")
+                           << errinfo_openssl_error(ERR_peek_error())
+                           << boost::errinfo_file_name(cakey));
+               }
+
+               STACK_OF(X509_NAME) *cert_names;
+
+               cert_names = SSL_load_client_CA_file(cakey.CStr());
+               if (cert_names == NULL) {
+                       msgbuf << "Error loading client ca key file '" << cakey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
+                       Log(LogCritical, "SSL", msgbuf.str());
+                       BOOST_THROW_EXCEPTION(openssl_error()
+                           << boost::errinfo_api_function("SSL_load_client_CA_file")
+                           << errinfo_openssl_error(ERR_peek_error())
+                           << boost::errinfo_file_name(cakey));
+               }
+
+               SSL_CTX_set_client_CA_list(sslContext.get(), cert_names);
        }
 
-       STACK_OF(X509_NAME) *cert_names;
-
-       cert_names = SSL_load_client_CA_file(cakey.CStr());
-       if (cert_names == NULL) {
-               msgbuf << "Error loading client ca key file '" << cakey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
-               Log(LogCritical, "SSL", msgbuf.str());
-               BOOST_THROW_EXCEPTION(openssl_error()
-                   << boost::errinfo_api_function("SSL_load_client_CA_file")
-                   << errinfo_openssl_error(ERR_peek_error())
-                   << boost::errinfo_file_name(cakey));
-       }
-
-       SSL_CTX_set_client_CA_list(sslContext.get(), cert_names);
-
        return sslContext;
 }
 
@@ -268,7 +271,7 @@ int MakeX509CSR(const String& cn, const String& keyfile, const String& csrfile,
                X509_NAME *subject = X509_NAME_new();
                X509_NAME_add_entry_by_txt(subject, "CN", MBSTRING_ASC, (unsigned char *)cn.CStr(), -1, -1, 0);
 
-               X509 *cert = CreateCert(key, subject, subject, key, ca);
+               shared_ptr<X509> cert = CreateCert(key, subject, subject, key, ca);
 
                X509_NAME_free(subject);
 
@@ -276,10 +279,8 @@ int MakeX509CSR(const String& cn, const String& keyfile, const String& csrfile,
 
                bio = BIO_new(BIO_s_file());
                BIO_write_filename(bio, const_cast<char *>(certfile.CStr()));
-               PEM_write_bio_X509(bio, cert);
+               PEM_write_bio_X509(bio, cert.get());
                BIO_free(bio);
-
-               X509_free(cert);
        }
 
        if (!csrfile.IsEmpty()) {
@@ -311,7 +312,7 @@ int MakeX509CSR(const String& cn, const String& keyfile, const String& csrfile,
        return 1;
 }
 
-X509 *CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca, const String& serialfile)
+shared_ptr<X509> CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca, const String& serialfile)
 {
        X509 *cert = X509_new();
        ASN1_INTEGER_set(X509_get_serialNumber(cert), 1);
@@ -337,7 +338,79 @@ X509 *CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PK
 
        X509_sign(cert, cakey, EVP_sha1());
 
-       return cert;
+       return shared_ptr<X509>(cert, X509_free);
+}
+
+String GetIcingaCADir(void)
+{
+       return Application::GetLocalStateDir() + "/lib/icinga2/ca";
+}
+
+shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject)
+{
+       std::stringstream msgbuf;
+       char errbuf[120];
+
+       String cadir = GetIcingaCADir();
+
+       String cakeyfile = cadir + "/ca.key";
+
+       RSA *rsa;
+
+       BIO *cakeybio = BIO_new_file(const_cast<char *>(cakeyfile.CStr()), "r");
+
+       if (!cakeybio) {
+               msgbuf << "Could not open CA key file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
+               Log(LogCritical, "SSL", msgbuf.str());
+               return shared_ptr<X509>();
+       }
+
+       rsa = PEM_read_bio_RSAPrivateKey(cakeybio, NULL, NULL, NULL);
+
+       if (!rsa) {
+               msgbuf << "Could not read RSA key from CA key file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
+               Log(LogCritical, "SSL", msgbuf.str());
+               return shared_ptr<X509>();
+       }
+
+       BIO_free(cakeybio);
+
+       String cacertfile = cadir + "/ca.crt";
+
+       shared_ptr<X509> cacert = GetX509Certificate(cacertfile);
+
+       EVP_PKEY *privkey = EVP_PKEY_new();
+       EVP_PKEY_assign_RSA(privkey, rsa);
+
+       return CreateCert(pubkey, subject, X509_get_subject_name(cacert.get()), privkey, false, cadir + "/serial.txt");
+}
+
+String CertificateToString(const shared_ptr<X509>& cert)
+{
+       BIO *mem = BIO_new(BIO_s_mem());
+       PEM_write_bio_X509(mem, cert.get());
+
+       char *data;
+       long len = BIO_get_mem_data(mem, &data);
+
+       String result = String(data, data + len);
+
+       BIO_free(mem);
+
+       return result;
+}
+
+String PBKDF2_SHA512(const String& password, const String& salt, int iterations)
+{
+       unsigned char digest[SHA512_DIGEST_LENGTH];
+       PKCS5_PBKDF2_HMAC(password.CStr(), password.GetLength(), reinterpret_cast<const unsigned char *>(salt.CStr()), salt.GetLength(),
+           iterations, EVP_sha512(), sizeof(digest), digest);
+
+       char output[SHA512_DIGEST_LENGTH*2+1];
+       for (int i = 0; i < 32; i++)
+               sprintf(output + 2 * i, "%02x", digest[i]);
+
+       return output;
 }
 
 String SHA256(const String& s)
@@ -371,9 +444,8 @@ String SHA256(const String& s)
                        << errinfo_openssl_error(ERR_peek_error()));
        }
 
-       int i;
        char output[SHA256_DIGEST_LENGTH*2+1];
-       for (i = 0; i < 32; i++)
+       for (int i = 0; i < 32; i++)
                sprintf(output + 2 * i, "%02x", digest[i]);
 
        return output;
index a2e8b6500772b9f6eecd2f5533d4ed48d4fd7651..bd272b88fbd6af831f873f6347f5f302eb166c07 100644 (file)
@@ -35,12 +35,16 @@ namespace icinga
 {
 
 void I2_BASE_API InitializeOpenSSL(void);
-shared_ptr<SSL_CTX> I2_BASE_API MakeSSLContext(const String& pubkey, const String& privkey, const String& cakey);
+shared_ptr<SSL_CTX> I2_BASE_API MakeSSLContext(const String& pubkey, const String& privkey, const String& cakey = String());
 void I2_BASE_API AddCRLToSSLContext(const shared_ptr<SSL_CTX>& context, const String& crlPath);
 String I2_BASE_API GetCertificateCN(const shared_ptr<X509>& certificate);
 shared_ptr<X509> I2_BASE_API GetX509Certificate(const String& pemfile);
 int I2_BASE_API MakeX509CSR(const String& cn, const String& keyfile, const String& csrfile = String(), const String& certfile = String(), bool ca = false);
-X509 * I2_BASE_API CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca, const String& serialfile = String());
+shared_ptr<X509> I2_BASE_API CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca, const String& serialfile = String());
+String I2_BASE_API GetIcingaCADir(void);
+String I2_BASE_API CertificateToString(const shared_ptr<X509>& cert);
+shared_ptr<X509> I2_BASE_API CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject);
+String I2_BASE_API PBKDF2_SHA512(const String& password, const String& salt, int iterations);
 String I2_BASE_API SHA256(const String& s);
 
 class I2_BASE_API openssl_error : virtual public std::exception, virtual public boost::exception { };
index b3c55551cb4dbe4c1ccd4477285588fa6b4dd9c0..0347ab8274b5f13132b5f22b7048112f4af133a6 100644 (file)
@@ -18,7 +18,7 @@
 set(cli_SOURCES
   featureenablecommand.cpp featuredisablecommand.cpp featurelistcommand.cpp
   objectlistcommand.cpp
-  pkinewcacommand.cpp pkinewcertcommand.cpp pkisigncsrcommand.cpp
+  pkinewcacommand.cpp pkinewcertcommand.cpp pkisigncsrcommand.cpp pkirequestcommand.cpp pkiticketcommand.cpp
   daemoncommand.cpp
 )
 
@@ -28,7 +28,7 @@ endif()
 
 add_library(cli SHARED ${cli_SOURCES})
 
-target_link_libraries(cli ${Boost_LIBRARIES} base config)
+target_link_libraries(cli ${Boost_LIBRARIES} base config remote)
 
 set_target_properties (
   cli PROPERTIES
index 2019c9828c409571ebd00c10b240fd64e4d6e3ce..d5af4d906b505784dbbc5cf7c329081d0f370f69 100644 (file)
@@ -68,73 +68,11 @@ int PKISignCSRCommand::Run(const boost::program_options::variables_map& vm, cons
 
        BIO_free(csrbio);
 
-       String cadir = Application::GetLocalStateDir() + "/lib/icinga2/ca";
+       shared_ptr<X509> cert = CreateCertIcingaCA(X509_REQ_get_pubkey(req), X509_REQ_get_subject_name(req));
 
-       String cakeyfile = cadir + "/ca.key";
+       X509_REQ_free(req);
 
-       RSA *rsa;
-
-       BIO *cakeybio = BIO_new_file(const_cast<char *>(cakeyfile.CStr()), "r");
-
-       if (!cakeybio) {
-               msgbuf << "Could not open CA key file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
-               Log(LogCritical, "SSL", msgbuf.str());
-               return 1;
-       }
-
-       rsa = PEM_read_bio_RSAPrivateKey(cakeybio, NULL, NULL, NULL);
-
-       if (!rsa) {
-               msgbuf << "Could not read RSA key from CA key file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
-               Log(LogCritical, "SSL", msgbuf.str());
-               return 1;
-       }
-
-       BIO_free(cakeybio);
-
-       String cacertfile = cadir + "/ca.crt";
-
-       BIO *cacertbio = BIO_new_file(const_cast<char *>(cacertfile.CStr()), "r");
-
-       if (!cacertbio) {
-               msgbuf << "Could not open CA certificate file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
-               Log(LogCritical, "SSL", msgbuf.str());
-               return 1;
-       }
-
-       X509 *cacert = PEM_read_bio_X509(cacertbio, NULL, NULL, NULL);
-
-       if (!cacert) {
-               msgbuf << "Could not read X509 certificate from CA certificate file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
-               Log(LogCritical, "SSL", msgbuf.str());
-               return 1;
-       }
-
-       BIO_free(cacertbio);
-
-       EVP_PKEY *privkey = EVP_PKEY_new();
-       EVP_PKEY_assign_RSA(privkey, rsa);
-
-       EVP_PKEY *pubkey = X509_REQ_get_pubkey(req);
-
-       X509 *cert = CreateCert(pubkey, X509_REQ_get_subject_name(req), X509_get_subject_name(cacert), privkey, false);
-
-       EVP_PKEY_free(pubkey);
-       X509_free(cacert);
-
-       BIO *certbio = BIO_new_fp(stdout, BIO_NOCLOSE);
-
-       if (!PEM_write_bio_X509(certbio, cert)) {
-               BIO_free(certbio);
-
-               msgbuf << "Could not write X509 certificate: " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
-               Log(LogCritical, "SSL", msgbuf.str());
-               return 1;
-       }
-
-       X509_free(cert);
-
-       BIO_free(certbio);
+       std::cout << CertificateToString(cert);
 
        return 0;
 }
index 9f57aa72e012752853a34a36e769c29990751d64..68dd690eb3a04e0311753030c05bd99c4508c0d2 100644 (file)
@@ -31,8 +31,10 @@ using namespace icinga;
 
 static Value SetLogPositionHandler(const MessageOrigin& origin, const Dictionary::Ptr& params);
 REGISTER_APIFUNCTION(SetLogPosition, log, &SetLogPositionHandler);
+static Value RequestCertificateHandler(const MessageOrigin& origin, const Dictionary::Ptr& params);
+REGISTER_APIFUNCTION(RequestCertificate, pki, &RequestCertificateHandler);
 
-ApiClient::ApiClient(const String& identity, bool authenticated, const Stream::Ptr& stream, ConnectionRole role)
+ApiClient::ApiClient(const String& identity, bool authenticated, const TlsStream::Ptr& stream, ConnectionRole role)
        : m_Identity(identity), m_Authenticated(authenticated), m_Stream(stream), m_Role(role), m_Seen(Utility::GetTime())
 {
        if (authenticated)
@@ -60,7 +62,7 @@ Endpoint::Ptr ApiClient::GetEndpoint(void) const
        return m_Endpoint;
 }
 
-Stream::Ptr ApiClient::GetStream(void) const
+TlsStream::Ptr ApiClient::GetStream(void) const
 {
        return m_Stream;
 }
@@ -220,3 +222,41 @@ Value SetLogPositionHandler(const MessageOrigin& origin, const Dictionary::Ptr&
 
        return Empty;
 }
+
+Value RequestCertificateHandler(const MessageOrigin& origin, const Dictionary::Ptr& params)
+{
+       if (!params)
+               return Empty;
+
+       ApiListener::Ptr listener = ApiListener::GetInstance();
+       String salt = listener->GetTicketSalt();
+
+       Dictionary::Ptr result = make_shared<Dictionary>();
+
+       if (salt.IsEmpty()) {
+               result->Set("error", "Ticket salt is not configured.");
+               return result;
+       }
+
+       String ticket = params->Get("ticket");
+       String realTicket = PBKDF2_SHA512(origin.FromClient->GetIdentity(), salt, 50000);
+
+       if (ticket != realTicket) {
+               result->Set("error", "Invalid ticket.");
+               return result;
+       }
+
+       shared_ptr<X509> cert = origin.FromClient->GetStream()->GetPeerCertificate();
+
+       EVP_PKEY *pubkey = X509_get_pubkey(cert.get());
+       X509_NAME *subject = X509_get_subject_name(cert.get());
+
+       shared_ptr<X509> newcert = CreateCertIcingaCA(pubkey, subject);
+       result->Set("cert", CertificateToString(newcert));
+
+       String cacertfile = GetIcingaCADir() + "/ca.crt";
+       shared_ptr<X509> cacert = GetX509Certificate(cacertfile);
+       result->Set("ca", CertificateToString(cacert));
+
+       return result;
+}
index 480eb576e7528e8c6e0d67209a7c9f68bda7428f..9c05a4f1ea323fa1baa4bd2b86077c9d134b54d4 100644 (file)
@@ -21,7 +21,7 @@
 #define APICLIENT_H
 
 #include "remote/endpoint.hpp"
-#include "base/stream.hpp"
+#include "base/tlsstream.hpp"
 #include "base/timer.hpp"
 #include "base/workqueue.hpp"
 #include "remote/i2-remote.hpp"
@@ -45,14 +45,14 @@ class I2_REMOTE_API ApiClient : public Object
 public:
        DECLARE_PTR_TYPEDEFS(ApiClient);
 
-       ApiClient(const String& identity, bool authenticated, const Stream::Ptr& stream, ConnectionRole role);
+       ApiClient(const String& identity, bool authenticated, const TlsStream::Ptr& stream, ConnectionRole role);
 
        void Start(void);
 
        String GetIdentity(void) const;
        bool IsAuthenticated(void) const;
        Endpoint::Ptr GetEndpoint(void) const;
-       Stream::Ptr GetStream(void) const;
+       TlsStream::Ptr GetStream(void) const;
        ConnectionRole GetRole(void) const;
 
        void Disconnect(void);
@@ -64,7 +64,7 @@ private:
        String m_Identity;
        bool m_Authenticated;
        Endpoint::Ptr m_Endpoint;
-       Stream::Ptr m_Stream;
+       TlsStream::Ptr m_Stream;
        ConnectionRole m_Role;
        double m_Seen;
 
index 75a3e1195e668a73b6fd56348028ea25b6cecede..90b29bbc51e6c5bfbf506808ea3486e6badcc1db 100644 (file)
@@ -18,6 +18,8 @@ class ApiListener : DynamicObject
 
        [config] bool accept_config;
 
+       [config] String ticket_salt;
+
        [state] double log_message_timestamp;
 
        String identity;
index c59a8e9187480a2cb52506581bbadfae69db658a..ae1b97fa1dd19597d2d5a27e68dfdc2850d3d58c 100644 (file)
@@ -32,7 +32,9 @@
        %attribute %string "bind_host",
        %attribute %string "bind_port",
 
-       %attribute %number "accept_config"
+       %attribute %number "accept_config",
+
+       %attribute %string "ticket_salt"
 }
 
 %type Endpoint {