diff options
-rw-r--r-- | BuildTools/SCons/SConscript.boot | 2 | ||||
-rw-r--r-- | Swiften/SASL/SConscript | 9 | ||||
-rw-r--r-- | Swiften/SASL/UnitTest/WindowsServicePrincipalNameTest.cpp | 161 | ||||
-rw-r--r-- | Swiften/SASL/WindowsServicePrincipalName.cpp | 106 | ||||
-rw-r--r-- | Swiften/SASL/WindowsServicePrincipalName.h | 71 |
5 files changed, 348 insertions, 1 deletions
diff --git a/BuildTools/SCons/SConscript.boot b/BuildTools/SCons/SConscript.boot index ff24eeb..2415fde 100644 --- a/BuildTools/SCons/SConscript.boot +++ b/BuildTools/SCons/SConscript.boot @@ -333,7 +333,7 @@ if env.get("coverage", 0) : env.Append(LINKFLAGS = ["-fprofile-arcs", "-ftest-coverage"]) if env["PLATFORM"] == "win32" : - env.Append(LIBS = ["user32", "crypt32", "dnsapi", "iphlpapi", "ws2_32", "wsock32", "Advapi32"]) + env.Append(LIBS = ["user32", "crypt32", "dnsapi", "iphlpapi", "ws2_32", "wsock32", "Advapi32", "ntdsapi"]) env.Append(CCFLAGS = ["/EHsc", "/nologo", "/Zm256"]) env.Append(LINKFLAGS = ["/INCREMENTAL:no", "/NOLOGO"]) if int(env["MSVS_VERSION"].split(".")[0]) < 10 : diff --git a/Swiften/SASL/SConscript b/Swiften/SASL/SConscript index 6509547..faf6320 100644 --- a/Swiften/SASL/SConscript +++ b/Swiften/SASL/SConscript @@ -11,6 +11,11 @@ objects = myenv.SwiftenObject([ "DIGESTMD5Properties.cpp", "DIGESTMD5ClientAuthenticator.cpp", ]) +if myenv["PLATFORM"] == "win32" : + objects += myenv.SwiftenObject([ + "WindowsServicePrincipalName.cpp", + ]) + swiften_env.Append(SWIFTEN_OBJECTS = [objects]) env.Append(UNITTEST_SOURCES = [ @@ -20,3 +25,7 @@ env.Append(UNITTEST_SOURCES = [ File("UnitTest/DIGESTMD5PropertiesTest.cpp"), File("UnitTest/DIGESTMD5ClientAuthenticatorTest.cpp"), ]) +if myenv["PLATFORM"] == "win32" : + env.Append(UNITTEST_SOURCES = [ + File("UnitTest/WindowsServicePrincipalNameTest.cpp"), + ]) diff --git a/Swiften/SASL/UnitTest/WindowsServicePrincipalNameTest.cpp b/Swiften/SASL/UnitTest/WindowsServicePrincipalNameTest.cpp new file mode 100644 index 0000000..1b0e6f6 --- /dev/null +++ b/Swiften/SASL/UnitTest/WindowsServicePrincipalNameTest.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> + +#include <Swiften/SASL/WindowsServicePrincipalName.h> + +using namespace Swift; + +class WindowsServicePrincipalNameTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(WindowsServicePrincipalNameTest); + CPPUNIT_TEST(testServiceClass); + CPPUNIT_TEST(testServiceName); + CPPUNIT_TEST(testInstanceName); + CPPUNIT_TEST(testInstancePort); + CPPUNIT_TEST(testReferrer); + CPPUNIT_TEST_SUITE_END(); + + public: + void testServiceClass() { + WindowsServicePrincipalName spn("mlink.adlon.isode.net"); + + CPPUNIT_ASSERT_EQUAL(spn.toString(), std::string("xmpp/mlink.adlon.isode.net")); + + spn.setServiceClass("ldap"); + CPPUNIT_ASSERT_EQUAL(spn.toString(), std::string("ldap/mlink.adlon.isode.net")); + + spn.setServiceClass("中文"); + CPPUNIT_ASSERT_EQUAL(spn.toString(), std::string("中文/mlink.adlon.isode.net")); + + try { + spn.setServiceClass(""); + spn.toString(); + CPPUNIT_ASSERT(false); + } catch (std::runtime_error) { + /* expected */ + } + + try { + spn.setServiceClass("xm/pp"); /* Foward slash not allowed */ + spn.toString(); + CPPUNIT_ASSERT(false); + } catch (std::runtime_error) { + /* expected */ + } + } + + void testServiceName() { + try { + WindowsServicePrincipalName spn(""); + spn.toString(); + CPPUNIT_ASSERT(false); + } catch (std::runtime_error) { + /* expected */ + } + + try { + WindowsServicePrincipalName spn2("mlink/adlon.isode.net"); /* Foward slash not allowed */ + spn2.toString(); + CPPUNIT_ASSERT(false); + } catch (std::runtime_error) { + /* expected */ + } + + WindowsServicePrincipalName spn3("mlinkÄ.adlon.isode.net"); + CPPUNIT_ASSERT_EQUAL(spn3.toString(), std::string("xmpp/mlinkÄ.adlon.isode.net")); + } + + void testInstanceName() { + WindowsServicePrincipalName spn("adlon.isode.net"); + + spn.setInstanceName("mlink.adlon.isode.net"); + CPPUNIT_ASSERT_EQUAL(spn.toString(), std::string("xmpp/mlink.adlon.isode.net/adlon.isode.net")); + + spn.setInstanceName("127.0.0.1"); + CPPUNIT_ASSERT_EQUAL(spn.toString(), std::string("xmpp/127.0.0.1/adlon.isode.net")); + + spn.setInstanceName(""); + CPPUNIT_ASSERT_EQUAL(spn.toString(), std::string("xmpp/adlon.isode.net")); + + spn.setInstanceName("cañón.adlon.isode.net"); + CPPUNIT_ASSERT_EQUAL(spn.toString(), std::string("xmpp/cañón.adlon.isode.net/adlon.isode.net")); + + try { + spn.setInstanceName("mlink/adlon.isode.net"); /* Foward slash not allowed */ + spn.toString(); + CPPUNIT_ASSERT(false); + } catch (std::runtime_error) { + /* expected */ + } + } + + void testInstancePort() { + WindowsServicePrincipalName spn("adlon.isode.net"); + + spn.setInstanceName("mlink.adlon.isode.net"); + spn.setInstancePort(6222); + CPPUNIT_ASSERT_EQUAL(spn.toString(), std::string("xmpp/mlink.adlon.isode.net:6222/adlon.isode.net")); + + spn.setInstancePort(0); + CPPUNIT_ASSERT_EQUAL(spn.toString(), std::string("xmpp/mlink.adlon.isode.net/adlon.isode.net")); + + WindowsServicePrincipalName spn2("mlink.adlon.isode.net"); + + spn2.setInstancePort(6222); + CPPUNIT_ASSERT_EQUAL(spn2.toString(), std::string("xmpp/mlink.adlon.isode.net:6222")); + + spn2.setInstancePort(0); + CPPUNIT_ASSERT_EQUAL(spn2.toString(), std::string("xmpp/mlink.adlon.isode.net")); + } + + void testReferrer() { + WindowsServicePrincipalName spn("127.0.0.1"); + + spn.setReferrer("referrer.net"); + CPPUNIT_ASSERT_EQUAL(spn.toString(), std::string("xmpp/127.0.0.1/referrer.net")); + + spn.setInstancePort(6222); + CPPUNIT_ASSERT_EQUAL(spn.toString(), std::string("xmpp/127.0.0.1:6222/referrer.net")); + + spn.setReferrer("हिन्दी.net"); + CPPUNIT_ASSERT_EQUAL(spn.toString(), std::string("xmpp/127.0.0.1:6222/हिन्दी.net")); + + try { + spn.setReferrer("referrer/net"); /* Foward slash not allowed */ + spn.toString(); + CPPUNIT_ASSERT(false); + } catch (std::runtime_error) { + /* expected */ + } + + try { + spn.setReferrer(""); /* seems like you must have referrer with an IP */ + spn.toString(); + CPPUNIT_ASSERT(false); + } catch (std::runtime_error) { + /* expected */ + } + + WindowsServicePrincipalName spn2("mlink.adlon.isode.net"); + + spn2.setReferrer("referrer.net"); /* Referrer ignored if service name is not IP */ + CPPUNIT_ASSERT_EQUAL(spn2.toString(), std::string("xmpp/mlink.adlon.isode.net")); + + spn2.setReferrer("referrer/net"); /* Referrer ignored if service name is not IP */ + CPPUNIT_ASSERT_EQUAL(spn2.toString(), std::string("xmpp/mlink.adlon.isode.net")); + + WindowsServicePrincipalName spn3("adlon.isode.net"); + + spn3.setInstanceName("mlink.adlon.isode.net"); + spn3.setInstancePort(6222); + spn3.setReferrer("referrer.net"); /* Referrer ignored if service name is not IP */ + CPPUNIT_ASSERT_EQUAL(spn3.toString(), std::string("xmpp/mlink.adlon.isode.net:6222/adlon.isode.net")); + + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(WindowsServicePrincipalNameTest); diff --git a/Swiften/SASL/WindowsServicePrincipalName.cpp b/Swiften/SASL/WindowsServicePrincipalName.cpp new file mode 100644 index 0000000..187e9ac --- /dev/null +++ b/Swiften/SASL/WindowsServicePrincipalName.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swiften/SASL/WindowsServicePrincipalName.h> + +#include <vector> + +#include <Ntdsapi.h> + +#include <Swiften/Base/ByteArray.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Base/String.h> + +namespace Swift { + +WindowsServicePrincipalName::WindowsServicePrincipalName(const std::string& serviceName) : serviceClass_(L"xmpp"), instancePort_(0) { + serviceName_ = convertStringToWString(serviceName); +} + +WindowsServicePrincipalName::~WindowsServicePrincipalName() { +} + +void WindowsServicePrincipalName::setServiceClass(const std::string& serviceClass) { + serviceClass_ = convertStringToWString(serviceClass); +} + +void WindowsServicePrincipalName::setInstanceName(const std::string& instanceName) { + instanceName_ = convertStringToWString(instanceName); +} + +void WindowsServicePrincipalName::setReferrer(const std::string& referrer) { + referrer_ = convertStringToWString(referrer); +} + +std::string WindowsServicePrincipalName::toString() { + DWORD length = 512; + DWORD status = ERROR_BUFFER_OVERFLOW; + bool firstCall = true; + std::string str; + + while (status == ERROR_BUFFER_OVERFLOW) { + std::vector<wchar_t> value(length); + + /* length after this call will contain the required length if current length is not enough - so the next call should succeed */ + status = dsMakeSpn(&length, vecptr(value)); + if (status == ERROR_SUCCESS) { + str = convertWStringToString(std::wstring(vecptr(value), length-1 /* trailing 0 character */)); + break; + } + + if ((firstCall == false) || (status != ERROR_BUFFER_OVERFLOW)) { + std::stringstream errorString; + boost::system::error_code errorCode(status, boost::system::system_category()); + + errorString << "Error creating Service Principal Name: status: 0x" << std::hex << status << ": " << errorCode.message(); + + /* Any other error will be a programming error */ + throw std::runtime_error(errorString.str()); + } + + firstCall = false; + } + + SWIFT_LOG(debug) << "SPN: " << str << std::endl; + return str; +} + +DWORD WindowsServicePrincipalName::dsMakeSpn(DWORD* length, wchar_t* value) { + DWORD status; + +#ifdef UNICODE + SWIFT_LOG(debug) << "UNICODE is defined" << std::endl; +#else + SWIFT_LOG(debug) << "UNICODE is not defined" << std::endl; +#endif + + SWIFT_LOG(debug) << "serviceClass_: " << convertWStringToString(serviceClass_.c_str()) << std::endl; + SWIFT_LOG(debug) << "serviceName_: " << convertWStringToString(serviceName_.c_str()) << std::endl; + SWIFT_LOG(debug) << "instanceName_: " << convertWStringToString(instanceName_.c_str()) << std::endl; + SWIFT_LOG(debug) << "referrer_: " << convertWStringToString(referrer_.c_str()) << std::endl; + SWIFT_LOG(debug) << "instancePort_: " << instancePort_ << std::endl; + SWIFT_LOG(debug) << "length: " << *length << std::endl; + + /* Call the Unicode function because that is recommended: +https://msdn.microsoft.com/en-us/library/windows/desktop/ff381407%28v=vs.85%29.aspx */ + status = DsMakeSpnW( + serviceClass_.c_str(), + serviceName_.c_str(), + instanceName_.empty() ? NULL : instanceName_.c_str(), + instancePort_, + referrer_.empty() ? NULL : referrer_.c_str(), + length, + value); + if (status != ERROR_SUCCESS) { + boost::system::error_code errorCode(status, boost::system::system_category()); + + SWIFT_LOG(debug) << std::hex << "status: 0x" << status << ": " << errorCode.message() << std::endl; + } + + return status; +} + +} diff --git a/Swiften/SASL/WindowsServicePrincipalName.h b/Swiften/SASL/WindowsServicePrincipalName.h new file mode 100644 index 0000000..dc97c93 --- /dev/null +++ b/Swiften/SASL/WindowsServicePrincipalName.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <string> + +#include <Windows.h> + +#include <Swiften/Base/API.h> + +namespace Swift { + /* + * This represents the SPN used on Windows to identify a service + * instance. + */ + class SWIFTEN_API WindowsServicePrincipalName { + public: + /* + * This assigns the provided service name, + * "xmpp" service class, no instance name or referrer, + * and default port. All of these (except the service + * name) can be set to other values using the provided + * methods. + * If the constructor is called with the service name + * "hostname.example.com" and the provided methods are + * not used to change the defaults, then toString() + * will return the SPN "xmpp/hostname.example.com". + */ + WindowsServicePrincipalName(const std::string& serviceName); + + ~WindowsServicePrincipalName(); + + void setServiceClass(const std::string& serviceClass); + + void setInstanceName(const std::string& instanceName); + + void setReferrer(const std::string& referrer); + + /* + * This sets a non-default port for the service. Note + * that the default value is 0 which indicates the + * default port for the service. So if the XMPP service + * is using the default port of 5222 for client + * connections, then do not set the port to 5222 but let + * it remain 0 to indicate that the default port is + * used. + */ + void setInstancePort(short int instancePort) { instancePort_ = instancePort; } + + /* + * This follows the rules of SPN creation on Windows and + * returns the SPN string constructed from the set + * values. + */ + std::string toString(); + + private: + DWORD dsMakeSpn(DWORD* length, wchar_t* value); + + private: + std::wstring serviceClass_; + std::wstring serviceName_; + std::wstring instanceName_; + USHORT instancePort_; + std::wstring referrer_; + }; +} |