    /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
    /*
     * This file is part of the LibreOffice project.
     *
     * This Source Code Form is subject to the terms of the Mozilla Public
     * License, v. 2.0. If a copy of the MPL was not distributed with this
     * file, You can obtain one at http://mozilla.org/MPL/2.0/.
     */

    #include "WinUserInfoBe.hxx"

    #include <com/sun/star/beans/Optional.hpp>
    #include <comphelper/base64.hxx>
    #include <comphelper/configurationhelper.hxx>
    #include <com/sun/star/container/XNameAccess.hpp>
    #include <com/sun/star/container/XNameReplace.hpp>
    #include <com/sun/star/util/XChangesBatch.hpp>
    #include <cppuhelper/supportsservice.hxx>
    #include <map>
    #include <o3tl/make_unique.hxx>
    #include <o3tl/char16_t2wchar_t.hxx>
    #include <sal/log.hxx>

    #include <Iads.h>
    #include <Adshlp.h>
    #include <Lmcons.h>
    #define SECURITY_WIN32
    #include <Security.h>

    namespace extensions
    {
    namespace config
    {
    namespace WinUserInfo
    {
    class WinUserInfoBe_Impl
    {
    public:
        virtual ~WinUserInfoBe_Impl(){};
        virtual OUString GetGivenName() = 0;
        virtual OUString GetSn() { return ""; }
        virtual OUString GetFathersname() { return ""; }
        virtual OUString GetInitials() { return ""; }
        virtual OUString GetStreet() { return ""; }
        virtual OUString GetCity() { return ""; }
        virtual OUString GetState() { return ""; }
        virtual OUString GetApartment() { return ""; }
        virtual OUString GetPostalCode() { return ""; }
        virtual OUString GetCountry() { return ""; }
        virtual OUString GetOrganization() { return ""; }
        virtual OUString GetPosition() { return ""; }
        virtual OUString GetTitle() { return ""; }
        virtual OUString GetHomePhone() { return ""; }
        virtual OUString GetTelephoneNumber() { return ""; }
        virtual OUString GetFaxNumber() { return ""; }
        virtual OUString GetMail() { return ""; }
    };
    }
    }
    }

    namespace
    {
    constexpr char givenname[]("givenname");
    constexpr char sn[]("sn");
    constexpr char fathersname[]("fathersname");
    constexpr char initials[]("initials");
    constexpr char street[]("street");
    constexpr char l[]("l");
    constexpr char st[]("st");
    constexpr char apartment[]("apartment");
    constexpr char postalcode[]("postalcode");
    constexpr char c[]("c");
    constexpr char o[]("o");
    constexpr char position[]("position");
    constexpr char title[]("title");
    constexpr char homephone[]("homephone");
    constexpr char telephonenumber[]("telephonenumber");
    constexpr char facsimiletelephonenumber[]("facsimiletelephonenumber");
    constexpr char mail[]("mail");

    // Backend class implementing access to Active Directory user data. It caches its encoded data
    // in a configuration entry, to allow reusing it when user later doesn't have access to AD DC
    // (otherwise the user would get different data when connected vs not connected).
    class ADsUserAccess : public extensions::config::WinUserInfo::WinUserInfoBe_Impl
    {
    public:
        ADsUserAccess(const css::uno::Reference<css::uno::XComponentContext>& xContext)
        {
            try
            {
                struct CoInitializeGuard
                {
                    CoInitializeGuard()
                    {
                        if (FAILED(CoInitialize(nullptr)))
                            throw css::uno::RuntimeException();
                    }
                    ~CoInitializeGuard() { CoUninitialize(); }
                } aCoInitializeGuard;

                IADsADSystemInfo* pADsys;
                HRESULT hr = CoCreateInstance(CLSID_ADSystemInfo, nullptr, CLSCTX_INPROC_SERVER,
                                              IID_IADsADSystemInfo, reinterpret_cast<void**>(&pADsys));
                if (FAILED(hr))
                    throw css::uno::RuntimeException();
                CoIfPtr<IADsADSystemInfo> aADsysGuard(pADsys);

                BSTR sUserDN;
                hr = pADsys->get_UserName(&sUserDN);
                if (FAILED(hr))
                    throw css::uno::RuntimeException();
                BSTRGuard aUserNameGuard(sUserDN, SysFreeString);
                // If this user is an AD user, then without an active connection to the domain, all the
                // above will succeed, and m_sUserDN will be correctly initialized, but the following
                // call to ADsGetObject will fail, and we will attempt reading cached values.
                m_sUserDN = o3tl::toU(sUserDN);
                OUString sLdapUserDN = "LDAP://" + m_sUserDN;
                IADsUser* pUser;
                hr = ADsGetObject(o3tl::toW(sLdapUserDN.getStr()), IID_IADsUser,
                                  reinterpret_cast<void**>(&pUser));
                if (FAILED(hr))
                    throw css::uno::RuntimeException();
                CoIfPtr<IADsUser> pUserGuard(pUser);
                // Fetch all the required information right now, when we know to have access to AD
                // (later the connection may already be lost)
                m_aMap[givenname] = Str(pUser, &IADsUser::get_FirstName);
                m_aMap[sn] = Str(pUser, &IADsUser::get_LastName);
                m_aMap[initials] = Str(pUser, L"initials");
                m_aMap[street] = Str(pUser, L"streetAddress");
                m_aMap[l] = Str(pUser, L"l");
                m_aMap[st] = Str(pUser, L"st");
                m_aMap[postalcode] = Str(pUser, L"postalCode");
                m_aMap[c] = Str(pUser, L"co");
                m_aMap[o] = Str(pUser, L"company");
                m_aMap[title] = Str(pUser, &IADsUser::get_Title);
                m_aMap[homephone] = Str(pUser, L"homePhone");
                m_aMap[telephonenumber] = Str(pUser, L"TelephoneNumber");
                m_aMap[facsimiletelephonenumber] = Str(pUser, L"facsimileTelephoneNumber");
                m_aMap[mail] = Str(pUser, &IADsUser::get_EmailAddress);

                CacheData(xContext);
            }
            catch (css::uno::Exception&)
            {
                // Maybe we temporarily lost connection to AD; try to get cached data
                GetCachedData(xContext);
            }
        }

        virtual OUString GetGivenName() override { return m_aMap[givenname]; }
        virtual OUString GetSn() override { return m_aMap[sn]; }
        virtual OUString GetInitials() override { return m_aMap[initials]; }
        virtual OUString GetStreet() override { return m_aMap[street]; }
        virtual OUString GetCity() override { return m_aMap[l]; }
        virtual OUString GetState() override { return m_aMap[st]; }
        virtual OUString GetPostalCode() override { return m_aMap[postalcode]; }
        virtual OUString GetCountry() override { return m_aMap[c]; }
        virtual OUString GetOrganization() override { return m_aMap[o]; }
        virtual OUString GetTitle() override { return m_aMap[title]; }
        virtual OUString GetHomePhone() override { return m_aMap[homephone]; }
        virtual OUString GetTelephoneNumber() override { return m_aMap[telephonenumber]; }
        virtual OUString GetFaxNumber() override { return m_aMap[facsimiletelephonenumber]; }
        virtual OUString GetMail() override { return m_aMap[mail]; }

    private:
        static void ReleaseIUnknown(IUnknown* p)
        {
            if (p)
                p->Release();
        }
        template <class If> class CoIfPtr : public std::unique_ptr<If, decltype(&ReleaseIUnknown)>
        {
        public:
            CoIfPtr(If* p = nullptr)
                : std::unique_ptr<If, decltype(&ReleaseIUnknown)>(p, ReleaseIUnknown)
            {
            }
        };
        typedef std::unique_ptr<OLECHAR, decltype(&SysFreeString)> BSTRGuard;

        typedef HRESULT (__stdcall IADsUser::*getstrfunc)(BSTR*);
        static OUString Str(IADsUser* pUser, getstrfunc func)
        {
            BSTR sBstr;
            if (FAILED((pUser->*func)(&sBstr)))
                return "";
            BSTRGuard aBstrGuard(sBstr, SysFreeString);
            return o3tl::toU(sBstr);
        }
        static OUString Str(IADsUser* pUser, const wchar_t* property)
        {
            BSTRGuard sBstrProp(SysAllocString(property), SysFreeString);
            struct AutoVariant : public VARIANT
            {
                AutoVariant() { VariantInit(this); }
                ~AutoVariant() { VariantClear(this); }
            } varArr;
            if (FAILED(pUser->GetEx(sBstrProp.get(), &varArr)))
                return "";
            SAFEARRAY* sa = V_ARRAY(&varArr);
            LONG nStart, nEnd;
            if (FAILED(SafeArrayGetLBound(sa, 1, &nStart)) || FAILED(SafeArrayGetUBound(sa, 1, &nEnd)))
                return "";
            AutoVariant varItem;
            for (LONG i = nStart; i <= nEnd; i++)
            {
                if (FAILED(SafeArrayGetElement(sa, &i, &varItem)))
                    continue;
                if (varItem.vt == VT_BSTR)
                    return o3tl::toU(V_BSTR(&varItem));
                VariantClear(&varItem);
            }
            return "";
        }

        void CacheData(const css::uno::Reference<css::uno::XComponentContext>& xContext)
        {
            try
            {
                const OUString sNul('\0');
                OUStringBuffer sCachedData(200);
                sCachedData.append("user=").append(m_sUserDN);
                sCachedData.append(sNul).append(givenname).append("=").append(GetGivenName());
                sCachedData.append(sNul).append(sn).append("=").append(GetSn());
                sCachedData.append(sNul).append(initials).append("=").append(GetInitials());
                sCachedData.append(sNul).append(street).append("=").append(GetStreet());
                sCachedData.append(sNul).append(l).append("=").append(GetCity());
                sCachedData.append(sNul).append(st).append("=").append(GetState());
                sCachedData.append(sNul).append(postalcode).append("=").append(GetPostalCode());
                sCachedData.append(sNul).append(c).append("=").append(GetCountry());
                sCachedData.append(sNul).append(o).append("=").append(GetOrganization());
                sCachedData.append(sNul).append(title).append("=").append(GetTitle());
                sCachedData.append(sNul).append(homephone).append("=").append(GetHomePhone());
                sCachedData.append(sNul)
                    .append(telephonenumber)
                    .append("=")
                    .append(GetTelephoneNumber());
                sCachedData.append(sNul)
                    .append(facsimiletelephonenumber)
                    .append("=")
                    .append(GetFaxNumber());
                sCachedData.append(sNul).append(mail).append("=").append(GetMail());
                const css::uno::Sequence<sal_Int8> seqCachedData(
                    reinterpret_cast<const sal_Int8*>(sCachedData.getStr()),
                    sCachedData.getLength() * sizeof(sal_Unicode));
                OUStringBuffer sOutBuf;
                comphelper::Base64::encode(sOutBuf, seqCachedData);

                auto xIface = comphelper::ConfigurationHelper::openConfig(
                    xContext, "org.openoffice.UserProfile/WinUserInfo",
                    comphelper::EConfigurationModes::Standard);
                css::uno::Reference<css::container::XNameReplace> xNameReplace(
                    xIface, css::uno::UNO_QUERY_THROW);
                xNameReplace->replaceByName("Cache", css::uno::makeAny(sOutBuf.makeStringAndClear()));

                css::uno::Reference<css::util::XChangesBatch> xChangesBatch(xIface,
                                                                            css::uno::UNO_QUERY_THROW);
                xChangesBatch->commitChanges();
            }
            catch (const css::uno::Exception& e)
            {
                SAL_WARN("extensions.config",
                         "ADsUserAccess: access to configuration data failed: " << e);
            }
        }

        void GetCachedData(const css::uno::Reference<css::uno::XComponentContext>& xContext)
        {
            if (m_sUserDN.isEmpty())
                throw css::uno::RuntimeException();

            auto xIface = comphelper::ConfigurationHelper::openConfig(
                xContext, "org.openoffice.UserProfile/WinUserInfo",
                comphelper::EConfigurationModes::ReadOnly);
            css::uno::Reference<css::container::XNameAccess> xNameAccess(xIface,
                                                                         css::uno::UNO_QUERY_THROW);
            OUString sCache;
            xNameAccess->getByName("Cache") >>= sCache;
            if (sCache.isEmpty())
                throw css::uno::RuntimeException();

            {
                css::uno::Sequence<sal_Int8> seqCachedData;
                comphelper::Base64::decode(seqCachedData, sCache);
                sCache = OUString(reinterpret_cast<const sal_Unicode*>(seqCachedData.getConstArray()),
                                  seqCachedData.getLength() / sizeof(sal_Unicode));
            }

            OUString sUserDN;
            std::map<const OUString, OUString> aMap;
            sal_Int32 nIndex = 0;
            do
            {
                const OUString sEntry = sCache.getToken(0, '\0', nIndex);
                sal_Int32 nEqIndex = 0;
                const OUString sEntryName = sEntry.getToken(0, '=', nEqIndex);
                OUString sEntryVal;
                if (nEqIndex >= 0)
                    sEntryVal = sEntry.copy(nEqIndex);
                if (sEntryName == "user")
                    sUserDN = sEntryVal;
                else
                    aMap[sEntryName] = sEntryVal;
            } while (nIndex >= 0);

            if (sUserDN != m_sUserDN)
                throw css::uno::RuntimeException();
            m_aMap = std::move(aMap);
        }

        OUString m_sUserDN; // used to check if the cached data is for current user
        std::map<const OUString, OUString> m_aMap;
    };

    class SysInfoUserAccess : public extensions::config::WinUserInfo::WinUserInfoBe_Impl
    {
    public:
        SysInfoUserAccess()
        {
            try
            {
                ULONG nSize = 0;
                GetUserNameExW(NameDisplay, nullptr, &nSize);
                if (GetLastError() != ERROR_MORE_DATA)
                    throw css::uno::RuntimeException();
                auto pNameBuf(o3tl::make_unique<wchar_t[]>(nSize));
                if (!GetUserNameExW(NameDisplay, pNameBuf.get(), &nSize))
                    throw css::uno::RuntimeException();
                m_sName = o3tl::toU(pNameBuf.get());
            }
            catch (css::uno::RuntimeException&)
            {
                // GetUserNameEx may fail in some cases (e.g., for built-in AD domain
                // administrator account on non-DC systems), where GetUserName will
                // still give a name.
                DWORD nSize = UNLEN + 1;
                auto pNameBuf(o3tl::make_unique<wchar_t[]>(nSize));
                if (!GetUserNameW(pNameBuf.get(), &nSize))
                    throw css::uno::RuntimeException();
                m_sName = o3tl::toU(pNameBuf.get());
            }
        }

        virtual OUString GetGivenName() override { return m_sName; }

    private:
        OUString m_sName;
    };
    }

    namespace extensions
    {
    namespace config
    {
    namespace WinUserInfo
    {
    WinUserInfoBe::WinUserInfoBe(const css::uno::Reference<css::uno::XComponentContext>& xContext)
        : WinUserInfoMutexHolder()
        , BackendBase(mMutex)
    {
        try
        {
            m_pImpl.reset(new ADsUserAccess(xContext));
        }
        catch (css::uno::RuntimeException&)
        {
            m_pImpl.reset(new SysInfoUserAccess);
        }
    }

    WinUserInfoBe::~WinUserInfoBe() {}

    void WinUserInfoBe::setPropertyValue(OUString const&, css::uno::Any const&)
    {
        throw css::lang::IllegalArgumentException("setPropertyValue not supported",
                                                  static_cast<cppu::OWeakObject*>(this), -1);
    }

    css::uno::Any WinUserInfoBe::getPropertyValue(OUString const& PropertyName)
    {
        OUString sValue;
        // Only process the first argument of possibly multiple space- or comma-separated arguments
        OUString sToken = PropertyName.getToken(0, ' ').getToken(0, ',');
        if (sToken == givenname)
        {
            sValue = m_pImpl->GetGivenName();
        }
        else if (sToken == sn)
        {
            sValue = m_pImpl->GetSn();
        }
        else if (sToken == fathersname)
        {
            sValue = m_pImpl->GetFathersname();
        }
        else if (sToken == initials)
        {
            sValue = m_pImpl->GetInitials();
        }
        else if (sToken == street)
        {
            sValue = m_pImpl->GetStreet();
        }
        else if (sToken == l)
        {
            sValue = m_pImpl->GetCity();
        }
        else if (sToken == st)
        {
            sValue = m_pImpl->GetState();
        }
        else if (sToken == apartment)
        {
            sValue = m_pImpl->GetApartment();
        }
        else if (sToken == postalcode)
        {
            sValue = m_pImpl->GetPostalCode();
        }
        else if (sToken == c)
        {
            sValue = m_pImpl->GetCountry();
        }
        else if (sToken == o)
        {
            sValue = m_pImpl->GetOrganization();
        }
        else if (sToken == position)
        {
            sValue = m_pImpl->GetPosition();
        }
        else if (sToken == title)
        {
            sValue = m_pImpl->GetTitle();
        }
        else if (sToken == homephone)
        {
            sValue = m_pImpl->GetHomePhone();
        }
        else if (sToken == telephonenumber)
        {
            sValue = m_pImpl->GetTelephoneNumber();
        }
        else if (sToken == facsimiletelephonenumber)
        {
            sValue = m_pImpl->GetFaxNumber();
        }
        else if (sToken == mail)
        {
            sValue = m_pImpl->GetMail();
        }
        else
            throw css::beans::UnknownPropertyException(sToken, static_cast<cppu::OWeakObject*>(this));

        return css::uno::makeAny(css::beans::Optional<css::uno::Any>(
            !sValue.isEmpty(), sValue.isEmpty() ? css::uno::Any() : css::uno::makeAny(sValue)));
    }

    OUString WinUserInfoBe::getWinUserInfoBeName()
    {
        return OUString("com.sun.star.comp.configuration.backend.WinUserInfoBe");
    }

    OUString SAL_CALL WinUserInfoBe::getImplementationName() { return getWinUserInfoBeName(); }

    css::uno::Sequence<OUString> WinUserInfoBe::getWinUserInfoBeServiceNames()
    {
        css::uno::Sequence<OUString> aServices{ "com.sun.star.configuration.backend.WinUserInfoBe" };
        return aServices;
    }

    sal_Bool SAL_CALL WinUserInfoBe::supportsService(const OUString& aServiceName)
    {
        return cppu::supportsService(this, aServiceName);
    }

    css::uno::Sequence<OUString> SAL_CALL WinUserInfoBe::getSupportedServiceNames()
    {
        return getWinUserInfoBeServiceNames();
    }
    }
    }
    }

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V745 A 'wchar_t *' type string is incorrectly converted to 'BSTR' type string. Consider using 'SysAllocString' function.