IFACEMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
    {
        return punkOuter ? CLASS_E_NOAGGREGATION : _pfnCreate(riid, ppv);
    }

/**************************************************************************

**************************************************************************/

#include <windows.h>
#include <shlwapi.h>
#include <objbase.h>
#include <Shlobj.h>
#include <olectl.h>
#include <strsafe.h>
#include "Utils.h"
#include <new>  // std::nothrow

// The GUID for the FolderViewImpl
#include "GUID.h"

#pragma comment(lib, "propsys.lib")
#pragma comment(lib, "shlwapi.lib")

HRESULT CFolderViewImplFolder_CreateInstance(REFIID riid, void **ppv);
HRESULT CFolderViewImplContextMenu_CreateInstance(REFIID riid, void **ppv);

typedef HRESULT (*PFNCREATEINSTANCE)(REFIID riid, void **ppvObject);
struct CLASS_OBJECT_INIT
{
    const CLSID *pClsid;
    PFNCREATEINSTANCE pfnCreate;
};

// add classes supported by this module here
const CLASS_OBJECT_INIT c_rgClassObjectInit[] =
{
    { &CLSID_FolderViewImpl,            CFolderViewImplFolder_CreateInstance },
    { &CLSID_FolderViewImplContextMenu, CFolderViewImplContextMenu_CreateInstance },
};

long g_cRefModule = 0;

// Handle the the DLL's module
HINSTANCE g_hInst = NULL;

// Standard DLL functions
STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void *)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        g_hInst = hInstance;
        DisableThreadLibraryCalls(hInstance);
    }
    return TRUE;
}

STDAPI DllCanUnloadNow()
{
    // Only allow the DLL to be unloaded after all outstanding references have been released
    return (g_cRefModule == 0) ? S_OK : S_FALSE;
}

void DllAddRef()
{
    InterlockedIncrement(&g_cRefModule);
}

void DllRelease()
{
    InterlockedDecrement(&g_cRefModule);
}

class CClassFactory : public IClassFactory
{
public:
    static HRESULT CreateInstance(REFCLSID clsid, const CLASS_OBJECT_INIT *pClassObjectInits, size_t cClassObjectInits, REFIID riid, void **ppv)
    {
        *ppv = NULL;
        HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
        for (size_t i = 0; i < cClassObjectInits; i++)
        {
            if (clsid == *pClassObjectInits[i].pClsid)
            {
                IClassFactory *pClassFactory = new (std::nothrow) CClassFactory(pClassObjectInits[i].pfnCreate);
                hr = pClassFactory ? S_OK : E_OUTOFMEMORY;
                if (SUCCEEDED(hr))
                {
                    hr = pClassFactory->QueryInterface(riid, ppv);
                    pClassFactory->Release();
                }
                break; // match found
            }
        }
        return hr;
    }

    CClassFactory(PFNCREATEINSTANCE pfnCreate) : _cRef(1), _pfnCreate(pfnCreate)
    {
        DllAddRef();
    }

    // IUnknown
    IFACEMETHODIMP QueryInterface(REFIID riid, void ** ppv)
    {
        static const QITAB qit[] =
        {
            QITABENT(CClassFactory, IClassFactory),
            { 0 }
        };
        return QISearch(this, qit, riid, ppv);
    }

    IFACEMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&_cRef);
    }

    IFACEMETHODIMP_(ULONG) Release()
    {
        long cRef = InterlockedDecrement(&_cRef);
        if (cRef == 0)
        {
            delete this;
        }
        return cRef;
    }

    // IClassFactory

    IFACEMETHODIMP LockServer(BOOL fLock)
    {
        if (fLock)
        {
            DllAddRef();
        }
        else
        {
            DllRelease();
        }
        return S_OK;
    }

private:
    ~CClassFactory()
    {
        DllRelease();
    }

    long _cRef;
    PFNCREATEINSTANCE _pfnCreate;
};

STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void **ppv)
{
    return CClassFactory::CreateInstance(clsid, c_rgClassObjectInit, ARRAYSIZE(c_rgClassObjectInit), riid, ppv);
}

#define MYCOMPUTER_NAMESPACE_GUID L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MyComputer\\NameSpace\\%s"

const WCHAR g_szExtTitle[] =  L"FolderView SDK Sample";

void RefreshFolderViews(UINT csidl)
{
    PIDLIST_ABSOLUTE pidl;
    if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, csidl, &pidl)))
    {
        SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_IDLIST, pidl, 0);
        CoTaskMemFree(pidl);
    }
}

// Structure to hold data for individual keys to register.
typedef struct
{
    HKEY  hRootKey;
    PCWSTR pszSubKey;
    PCWSTR pszClassID;
    PCWSTR pszValueName;
    BYTE *pszData;
    DWORD dwType;
} REGSTRUCT;

//  1. The classID must be created under HKCR\CLSID.
//     a. DefaultIcon must be set to <Path and Module>,0.
//     b. InprocServer32 set to path and module.
//        i. Threading model specified as Apartment.
//     c. ShellFolder attributes must be set.
//  2. If the extension in non-rooted, its GUID is entered at the desired folder.
//  3. It must be registered as approved for Windows NT or XP.
STDAPI DllRegisterServer()
{
    WCHAR szFolderViewImplClassID[64], szContextMenuClassID[64], szSubKey[MAX_PATH], szData[MAX_PATH];
    StringFromGUID2(CLSID_FolderViewImpl, szFolderViewImplClassID, ARRAYSIZE(szFolderViewImplClassID));      // Convert the IID to a string.
    StringFromGUID2(CLSID_FolderViewImplContextMenu, szContextMenuClassID, ARRAYSIZE(szContextMenuClassID)); // Convert the IID to a string.

    // Get the path and module name.
    WCHAR szModulePathAndName[MAX_PATH];
    GetModuleFileName(g_hInst, szModulePathAndName, ARRAYSIZE(szModulePathAndName));

    // This will setup and register the basic ClassIDs.
    DWORD dwData = SFGAO_FOLDER | SFGAO_HASSUBFOLDER  | SFGAO_CANDELETE;
    REGSTRUCT rgRegEntries[] =
    {
        HKEY_CURRENT_USER,   L"Software\\Classes\\CLSID\\%s",                  szFolderViewImplClassID, NULL,                   (LPBYTE)g_szExtTitle,       REG_SZ,
        HKEY_CURRENT_USER,   L"Software\\Classes\\CLSID\\%s\\InprocServer32",  szFolderViewImplClassID, NULL,                   (LPBYTE)L"%s",              REG_SZ,
        HKEY_CURRENT_USER,   L"Software\\Classes\\CLSID\\%s\\InprocServer32",  szFolderViewImplClassID, L"ThreadingModel",      (LPBYTE)L"Apartment",       REG_SZ,
        HKEY_CURRENT_USER,   L"Software\\Classes\\CLSID\\%s\\DefaultIcon",     szFolderViewImplClassID, NULL,                   (LPBYTE)L"shell32.dll,-42", REG_SZ,
        HKEY_CURRENT_USER,   L"Software\\Classes\\CLSID\\%s\\ShellFolder",     szFolderViewImplClassID, L"Attributes",          (LPBYTE)&dwData,            REG_DWORD,
        HKEY_CURRENT_USER,   L"Software\\Classes\\CLSID\\%s",                  szContextMenuClassID,    NULL,                   (LPBYTE)g_szExtTitle,       REG_SZ,
        HKEY_CURRENT_USER,   L"Software\\Classes\\CLSID\\%s\\InprocServer32",  szContextMenuClassID,    NULL,                   (LPBYTE)L"%s",              REG_SZ,
        HKEY_CURRENT_USER,   L"Software\\Classes\\CLSID\\%s\\InprocServer32",  szContextMenuClassID,    L"ThreadingModel",      (LPBYTE)L"Apartment",       REG_SZ,

        // For performance, only context menu verbs that register this are considered when the user double-clicks.
        HKEY_CURRENT_USER,   L"Software\\Classes\\CLSID\\%s\\ShellEx\\MayChangeDefaultMenu",                szContextMenuClassID, NULL,  (LPBYTE)L"",                  REG_SZ,
        // register the context menu handler under the FolderViewSampleType type.
        HKEY_CURRENT_USER,   L"Software\\Classes\\FolderViewSampleType\\shellex\\ContextMenuHandlers\\%s",  szContextMenuClassID, NULL,  (LPBYTE)szContextMenuClassID, REG_SZ,
    };

    HKEY hKey = NULL;
    HRESULT hr = S_OK;

    for (int i = 0; SUCCEEDED(hr) && (i < ARRAYSIZE(rgRegEntries)); i++)
    {
        // Create the sub key string.
        hr = StringCchPrintf(szSubKey, ARRAYSIZE(szSubKey), rgRegEntries[i].pszSubKey, rgRegEntries[i].pszClassID);
        if (SUCCEEDED(hr))
        {
            long lResult = RegCreateKeyEx(rgRegEntries[i].hRootKey, szSubKey, 0, NULL,
                                          REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
            if (ERROR_SUCCESS == lResult)
            {
                // If this is a string entry, create the string.
                if (REG_SZ == rgRegEntries[i].dwType)
                {
                    hr = StringCchPrintf(szData, ARRAYSIZE(szData), (LPWSTR)rgRegEntries[i].pszData, szModulePathAndName);
                    if (SUCCEEDED(hr))
                    {
                        RegSetValueEx(hKey,
                                      rgRegEntries[i].pszValueName,
                                      0,
                                      rgRegEntries[i].dwType,
                                      (LPBYTE)szData,
                                      (lstrlen(szData) + 1) * sizeof(WCHAR));
                    }
                }
                else if (REG_DWORD == rgRegEntries[i].dwType)
                {
                    RegSetValueEx(hKey,
                                  rgRegEntries[i].pszValueName,
                                  0, rgRegEntries[i].dwType,
                                  rgRegEntries[i].pszData,
                                  sizeof(DWORD));
                }

                RegCloseKey(hKey);
            }
            else
            {
                hr = SELFREG_E_CLASS;
            }
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = SELFREG_E_CLASS;

        // Now we are ready to register the namespace extension.
        // This will put our extension in My Computer.
        if (SUCCEEDED(StringCchPrintf(szSubKey, ARRAYSIZE(szSubKey), MYCOMPUTER_NAMESPACE_GUID, szFolderViewImplClassID)))
        {
            long lResult = RegCreateKeyEx(HKEY_CURRENT_USER, szSubKey, 0, NULL,
                                          REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
            if (ERROR_SUCCESS == lResult)
            {
                // Copy our name into the string.
                hr = StringCchCopy(szData, ARRAYSIZE(szData), g_szExtTitle);
                if (SUCCEEDED(hr))
                {
                    // Set the name of our extension.
                    lResult = RegSetValueEx(hKey, NULL, 0, REG_SZ, (LPBYTE)szData, (lstrlen(szData) + 1) * sizeof(szData[0]));
                    RegCloseKey(hKey);

                    // The Shell has to be notified that the change has been made.
                    RefreshFolderViews(CSIDL_DRIVES);
                }
            }
        }
    }

    if (SUCCEEDED(hr))
    {
        // Note: The property description file needs to be in the same
        // directory as ExplorerDataProvider.dll when registering the DLL.
        // Opportunistically attempt to register -- if regsvr32 is run elevated
        // then this will succeed, otherwise it will fail and some functionality
        // (such as exposing properties in the details pane) will not work.
        PSRegisterPropertySchema(L"ExplorerDataProvider.propdesc");
    }

    if (FAILED(hr))
    {
        // Remove the stuff we added.
        DllUnregisterServer();
    }

    return hr;
}

// Registry keys are removed here.
STDAPI DllUnregisterServer()
{
    WCHAR szSubKey[MAX_PATH], szFolderClassID[MAX_PATH], szContextMenuClassID[MAX_PATH];

    //Delete the context menu entries.
    StringFromGUID2(CLSID_FolderViewImplContextMenu, szContextMenuClassID, ARRAYSIZE(szContextMenuClassID));
    HRESULT hrCM = StringCchPrintf(szSubKey, ARRAYSIZE(szSubKey), L"CLSID\\%s", szContextMenuClassID);
    if (SUCCEEDED(hrCM))
    {
        hrCM = HRESULT_FROM_WIN32(RegDeleteTree(HKEY_CLASSES_ROOT, szSubKey));
        if (hrCM == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
        {
            hrCM = S_OK;
        }
    }

    // Delete the foldertype key.
    HRESULT hrFT = HRESULT_FROM_WIN32(RegDeleteTree(HKEY_CLASSES_ROOT, L"FolderViewSampleType"));
    if (hrFT == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
    {
        hrFT = S_OK;
    }

    // Delete the namespace extension entries
    StringFromGUID2(CLSID_FolderViewImpl, szFolderClassID, ARRAYSIZE(szFolderClassID));
    HRESULT hrSF = StringCchPrintf(szSubKey, ARRAYSIZE(szSubKey), MYCOMPUTER_NAMESPACE_GUID, szFolderClassID);
    if (SUCCEEDED(hrSF))
    {
        hrSF = HRESULT_FROM_WIN32(RegDeleteTree(HKEY_CURRENT_USER, szSubKey));
        if (hrSF == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
        {
            hrSF = S_OK;
        }
        if (SUCCEEDED(hrSF))
        {
            // Delete the object's registry entries
            hrSF = StringCchPrintf(szSubKey, ARRAYSIZE(szSubKey), L"CLSID\\%s", szFolderClassID);
            if (SUCCEEDED(hrSF))
            {
                hrSF = HRESULT_FROM_WIN32(RegDeleteTree(HKEY_CLASSES_ROOT, szSubKey));
                if (hrSF == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
                {
                    hrSF = S_OK;
                }
                if (SUCCEEDED(hrSF))
                {
                    // Refresh the folder views that might be open
                    RefreshFolderViews(CSIDL_DRIVES);
                }
            }
        }
    }

    // Remove the property description, this is expected to fail if it is not run elevated.
    PSUnregisterPropertySchema(L"ExplorerDataProvider.propdesc");

    return (SUCCEEDED(hrCM) && SUCCEEDED(hrSF) && SUCCEEDED(hrFT)) ? S_OK : SELFREG_E_CLASS;
}


try { py::module::import("numpy"); }
    catch (...) { return; }

/*
    tests/test_numpy_array.cpp -- test core array functionality

*/

#include "pybind11_tests.h"

#include <pybind11/numpy.h>
#include <pybind11/stl.h>

#include <cstdint>

using arr = py::array;
using arr_t = py::array_t<uint16_t, 0>;
static_assert(std::is_same<arr_t::value_type, uint16_t>::value, "");

template<typename... Ix> arr data(const arr& a, Ix... index) {
    return arr(a.nbytes() - a.offset_at(index...), (const uint8_t *) a.data(index...));
}

template<typename... Ix> arr data_t(const arr_t& a, Ix... index) {
    return arr(a.size() - a.index_at(index...), a.data(index...));
}

template<typename... Ix> arr& mutate_data(arr& a, Ix... index) {
    auto ptr = (uint8_t *) a.mutable_data(index...);
    for (ssize_t i = 0; i < a.nbytes() - a.offset_at(index...); i++)
        ptr[i] = (uint8_t) (ptr[i] * 2);
    return a;
}

template<typename... Ix> arr_t& mutate_data_t(arr_t& a, Ix... index) {
    auto ptr = a.mutable_data(index...);
    for (ssize_t i = 0; i < a.size() - a.index_at(index...); i++)
        ptr[i]++;
    return a;
}

template<typename... Ix> ssize_t index_at(const arr& a, Ix... idx) { return a.index_at(idx...); }
template<typename... Ix> ssize_t index_at_t(const arr_t& a, Ix... idx) { return a.index_at(idx...); }
template<typename... Ix> ssize_t offset_at(const arr& a, Ix... idx) { return a.offset_at(idx...); }
template<typename... Ix> ssize_t offset_at_t(const arr_t& a, Ix... idx) { return a.offset_at(idx...); }
template<typename... Ix> ssize_t at_t(const arr_t& a, Ix... idx) { return a.at(idx...); }
template<typename... Ix> arr_t& mutate_at_t(arr_t& a, Ix... idx) { a.mutable_at(idx...)++; return a; }

#define def_index_fn(name, type) \
    sm.def(#name, [](type a) { return name(a); }); \
    sm.def(#name, [](type a, int i) { return name(a, i); }); \
    sm.def(#name, [](type a, int i, int j) { return name(a, i, j); }); \
    sm.def(#name, [](type a, int i, int j, int k) { return name(a, i, j, k); });

template <typename T, typename T2> py::handle auxiliaries(T &&r, T2 &&r2) {
    if (r.ndim() != 2) throw std::domain_error("error: ndim != 2");
    py::list l;
    l.append(*r.data(0, 0));
    l.append(*r2.mutable_data(0, 0));
    l.append(r.data(0, 1) == r2.mutable_data(0, 1));
    l.append(r.ndim());
    l.append(r.itemsize());
    l.append(r.shape(0));
    l.append(r.shape(1));
    l.append(r.size());
    l.append(r.nbytes());
    return l.release();
}

TEST_SUBMODULE(numpy_array, sm) {

    // test_array_attributes
    sm.def("ndim", [](const arr& a) { return a.ndim(); });
    sm.def("shape", [](const arr& a) { return arr(a.ndim(), a.shape()); });
    sm.def("shape", [](const arr& a, ssize_t dim) { return a.shape(dim); });
    sm.def("strides", [](const arr& a) { return arr(a.ndim(), a.strides()); });
    sm.def("strides", [](const arr& a, ssize_t dim) { return a.strides(dim); });
    sm.def("writeable", [](const arr& a) { return a.writeable(); });
    sm.def("size", [](const arr& a) { return a.size(); });
    sm.def("itemsize", [](const arr& a) { return a.itemsize(); });
    sm.def("nbytes", [](const arr& a) { return a.nbytes(); });
    sm.def("owndata", [](const arr& a) { return a.owndata(); });

    // test_index_offset
    def_index_fn(index_at, const arr&);
    def_index_fn(index_at_t, const arr_t&);
    def_index_fn(offset_at, const arr&);
    def_index_fn(offset_at_t, const arr_t&);
    // test_data
    def_index_fn(data, const arr&);
    def_index_fn(data_t, const arr_t&);
    // test_mutate_data, test_mutate_readonly
    def_index_fn(mutate_data, arr&);
    def_index_fn(mutate_data_t, arr_t&);
    def_index_fn(at_t, const arr_t&);
    def_index_fn(mutate_at_t, arr_t&);

    // test_make_c_f_array
    sm.def("make_f_array", [] { return py::array_t<float>({ 2, 2 }, { 4, 8 }); });
    sm.def("make_c_array", [] { return py::array_t<float>({ 2, 2 }, { 8, 4 }); });

    // test_wrap
    sm.def("wrap", [](py::array a) {
        return py::array(
            a.dtype(),
            {a.shape(), a.shape() + a.ndim()},
            {a.strides(), a.strides() + a.ndim()},
            a.data(),
            a
        );
    });

    // test_numpy_view
    struct ArrayClass {
        int data[2] = { 1, 2 };
        ArrayClass() { py::print("ArrayClass()"); }
        ~ArrayClass() { py::print("~ArrayClass()"); }
    };
    py::class_<ArrayClass>(sm, "ArrayClass")
        .def(py::init<>())
        .def("numpy_view", [](py::object &obj) {
            py::print("ArrayClass::numpy_view()");
            ArrayClass &a = obj.cast<ArrayClass&>();
            return py::array_t<int>({2}, {4}, a.data, obj);
        }
    );

    // test_cast_numpy_int64_to_uint64
    sm.def("function_taking_uint64", [](uint64_t) { });

    // test_isinstance
    sm.def("isinstance_untyped", [](py::object yes, py::object no) {
        return py::isinstance<py::array>(yes) && !py::isinstance<py::array>(no);
    });
    sm.def("isinstance_typed", [](py::object o) {
        return py::isinstance<py::array_t<double>>(o) && !py::isinstance<py::array_t<int>>(o);
    });

    // test_constructors
    sm.def("default_constructors", []() {
        return py::dict(
            "array"_a=py::array(),
            "array_t<int32>"_a=py::array_t<std::int32_t>(),
            "array_t<double>"_a=py::array_t<double>()
        );
    });
    sm.def("converting_constructors", [](py::object o) {
        return py::dict(
            "array"_a=py::array(o),
            "array_t<int32>"_a=py::array_t<std::int32_t>(o),
            "array_t<double>"_a=py::array_t<double>(o)
        );
    });

    // test_overload_resolution
    sm.def("overloaded", [](py::array_t<double>) { return "double"; });
    sm.def("overloaded", [](py::array_t<float>) { return "float"; });
    sm.def("overloaded", [](py::array_t<int>) { return "int"; });
    sm.def("overloaded", [](py::array_t<unsigned short>) { return "unsigned short"; });
    sm.def("overloaded", [](py::array_t<long long>) { return "long long"; });
    sm.def("overloaded", [](py::array_t<std::complex<double>>) { return "double complex"; });
    sm.def("overloaded", [](py::array_t<std::complex<float>>) { return "float complex"; });

    sm.def("overloaded2", [](py::array_t<std::complex<double>>) { return "double complex"; });
    sm.def("overloaded2", [](py::array_t<double>) { return "double"; });
    sm.def("overloaded2", [](py::array_t<std::complex<float>>) { return "float complex"; });
    sm.def("overloaded2", [](py::array_t<float>) { return "float"; });

    // Only accept the exact types:
    sm.def("overloaded3", [](py::array_t<int>) { return "int"; }, py::arg().noconvert());
    sm.def("overloaded3", [](py::array_t<double>) { return "double"; }, py::arg().noconvert());

    // Make sure we don't do unsafe coercion (e.g. float to int) when not using forcecast, but
    // rather that float gets converted via the safe (conversion to double) overload:
    sm.def("overloaded4", [](py::array_t<long long, 0>) { return "long long"; });
    sm.def("overloaded4", [](py::array_t<double, 0>) { return "double"; });

    // But we do allow conversion to int if forcecast is enabled (but only if no overload matches
    // without conversion)
    sm.def("overloaded5", [](py::array_t<unsigned int>) { return "unsigned int"; });
    sm.def("overloaded5", [](py::array_t<double>) { return "double"; });

    // test_greedy_string_overload
    // Issue 685: ndarray shouldn't go to std::string overload
    sm.def("issue685", [](std::string) { return "string"; });
    sm.def("issue685", [](py::array) { return "array"; });
    sm.def("issue685", [](py::object) { return "other"; });

    // test_array_unchecked_fixed_dims
    sm.def("proxy_add2", [](py::array_t<double> a, double v) {
        auto r = a.mutable_unchecked<2>();
        for (ssize_t i = 0; i < r.shape(0); i++)
            for (ssize_t j = 0; j < r.shape(1); j++)
                r(i, j) += v;
    }, py::arg().noconvert(), py::arg());

    sm.def("proxy_init3", [](double start) {
        py::array_t<double, py::array::c_style> a({ 3, 3, 3 });
        auto r = a.mutable_unchecked<3>();
        for (ssize_t i = 0; i < r.shape(0); i++)
        for (ssize_t j = 0; j < r.shape(1); j++)
        for (ssize_t k = 0; k < r.shape(2); k++)
            r(i, j, k) = start++;
        return a;
    });
    sm.def("proxy_init3F", [](double start) {
        py::array_t<double, py::array::f_style> a({ 3, 3, 3 });
        auto r = a.mutable_unchecked<3>();
        for (ssize_t k = 0; k < r.shape(2); k++)
        for (ssize_t j = 0; j < r.shape(1); j++)
        for (ssize_t i = 0; i < r.shape(0); i++)
            r(i, j, k) = start++;
        return a;
    });
    sm.def("proxy_squared_L2_norm", [](py::array_t<double> a) {
        auto r = a.unchecked<1>();
        double sumsq = 0;
        for (ssize_t i = 0; i < r.shape(0); i++)
            sumsq += r[i] * r(i); // Either notation works for a 1D array
        return sumsq;
    });

    sm.def("proxy_auxiliaries2", [](py::array_t<double> a) {
        auto r = a.unchecked<2>();
        auto r2 = a.mutable_unchecked<2>();
        return auxiliaries(r, r2);
    });

    // test_array_unchecked_dyn_dims
    // Same as the above, but without a compile-time dimensions specification:
    sm.def("proxy_add2_dyn", [](py::array_t<double> a, double v) {
        auto r = a.mutable_unchecked();
        if (r.ndim() != 2) throw std::domain_error("error: ndim != 2");
        for (ssize_t i = 0; i < r.shape(0); i++)
            for (ssize_t j = 0; j < r.shape(1); j++)
                r(i, j) += v;
    }, py::arg().noconvert(), py::arg());
    sm.def("proxy_init3_dyn", [](double start) {
        py::array_t<double, py::array::c_style> a({ 3, 3, 3 });
        auto r = a.mutable_unchecked();
        if (r.ndim() != 3) throw std::domain_error("error: ndim != 3");
        for (ssize_t i = 0; i < r.shape(0); i++)
        for (ssize_t j = 0; j < r.shape(1); j++)
        for (ssize_t k = 0; k < r.shape(2); k++)
            r(i, j, k) = start++;
        return a;
    });
    sm.def("proxy_auxiliaries2_dyn", [](py::array_t<double> a) {
        return auxiliaries(a.unchecked(), a.mutable_unchecked());
    });

    sm.def("array_auxiliaries2", [](py::array_t<double> a) {
        return auxiliaries(a, a);
    });

    // test_array_failures
    // Issue #785: Uninformative "Unknown internal error" exception when constructing array from empty object:
    sm.def("array_fail_test", []() { return py::array(py::object()); });
    sm.def("array_t_fail_test", []() { return py::array_t<double>(py::object()); });
    // Make sure the error from numpy is being passed through:
    sm.def("array_fail_test_negative_size", []() { int c = 0; return py::array(-1, &c); });

    // test_initializer_list
    // Issue (unnumbered; reported in #788): regression: initializer lists can be ambiguous
    sm.def("array_initializer_list1", []() { return py::array_t<float>(1); }); // { 1 } also works, but clang warns about it
    sm.def("array_initializer_list2", []() { return py::array_t<float>({ 1, 2 }); });
    sm.def("array_initializer_list3", []() { return py::array_t<float>({ 1, 2, 3 }); });
    sm.def("array_initializer_list4", []() { return py::array_t<float>({ 1, 2, 3, 4 }); });

    // test_array_resize
    // reshape array to 2D without changing size
    sm.def("array_reshape2", [](py::array_t<double> a) {
        const ssize_t dim_sz = (ssize_t)std::sqrt(a.size());
        if (dim_sz * dim_sz != a.size())
            throw std::domain_error("array_reshape2: input array total size is not a squared integer");
        a.resize({dim_sz, dim_sz});
    });

    // resize to 3D array with each dimension = N
    sm.def("array_resize3", [](py::array_t<double> a, size_t N, bool refcheck) {
        a.resize({N, N, N}, refcheck);
    });

    // test_array_create_and_resize
    // return 2D array with Nrows = Ncols = N
    sm.def("create_and_resize", [](size_t N) {
        py::array_t<double> a;
        a.resize({N, N});
        std::fill(a.mutable_data(), a.mutable_data() + a.size(), 42.);
        return a;
    });
}


void setTranslate(int x,
                      int y) {_translateX = x; _translateY = y; }

/* ***** BEGIN LICENSE BLOCK *****
 * This file is part of openfx-misc <http://example.com>,
 *
 * ***** END LICENSE BLOCK ***** */

/*
 * OFX ReConverge plugin.
 * Shift convergence so that tracked point appears at screen-depth.
 * The ReConverge node only shifts views horizontally, not vertically.
 */

#ifdef DEBUG

#include <cstdio>
#include <cstdlib>
#include <cfloat> // DBL_MAX

#include "ofxsProcessing.H"
#include "ofxsPositionInteract.h"
#include "ofxsMacros.h"
#include "ofxsCoords.h"
#include "ofxsThreadSuite.h"

using namespace OFX;

OFXS_NAMESPACE_ANONYMOUS_ENTER

#define kPluginName "ReConvergeOFX"
#define kPluginGrouping "Views/Stereo"
#define kPluginDescription "Shift convergence so that a tracked point appears at screen-depth. " \
    "Horizontal disparity may be provided in the red channel of the " \
    "disparity input if it has RGBA components, or the Alpha channel " \
    "if it only has Alpha. " \
    "If no disparity is given, only the offset is taken into account. " \
    "The amount of shift in pixels is rounded to the closest integer. " \
    "The ReConverge node only shifts views horizontally, not vertically."
#define kPluginIdentifier "net.sf.openfx.reConvergePlugin"
#define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
#define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.

#define kSupportsTiles 1
#define kSupportsMultiResolution 1
#define kSupportsRenderScale 1
#define kSupportsMultipleClipPARs false
#define kSupportsMultipleClipDepths false
#define kRenderThreadSafety eRenderFullySafe

#define kParamConvergePoint "convergePoint"
#define kParamConvergePointLabel "Converge Upon"
#define kParamConvergePointHint "Position of the tracked point when the convergence is set"

#define kParamInteractive "interactive"
#define kParamInteractiveLabel "Interactive"
#define kParamInteractiveHint \
    "When checked the image will be rendered whenever moving the overlay interact instead of when releasing the mouse button."

#define kParamOffset "offset"
#define kParamOffsetLabel "Convergence Offset"
#define kParamOffsetHint "The disparity of the tracked point will be set to this"

#define kParamConvergeMode "convergeMode"
#define kParamConvergeModeLabel "Mode"
#define kParamConvergeModeHint "Select to view to be shifted in order to set convergence"
#define kParamConvergeModeOptionShiftRight "Shift Right", "Shift right view.", "right"
#define kParamConvergeModeOptionShiftLeft "Shift Left", "Shift left view.", "left"
#define kParamConvergeModeOptionShiftBoth "Shift Both", "Shift both views in opposite directions.", "both"

#define kClipDisparity "Disparity"

// Base class for the RGBA and the Alpha processor
// This class performs a translation by an integer number of pixels (x,y)
class TranslateBase
    : public ImageProcessor
{
protected:
    const Image *_srcImg;
    int _translateX;
    int _translateY;

public:
    /** @brief no arg ctor */
    TranslateBase(ImageEffect &instance)
        : ImageProcessor(instance)
        , _srcImg(NULL)
        , _translateX(0)
        , _translateY(0)
    {
    }

    /** @brief set the src image */
    void setSrcImg(const Image *v) {_srcImg = v; }

    /** @brief set the translation vector */

};

// template to do the RGBA processing
template <class PIX, int nComponents, int max>
class ImageTranslator
    : public TranslateBase
{
public:
    // ctor
    ImageTranslator(ImageEffect &instance)
        : TranslateBase(instance)
    {}

private:
    // and do some processing
    void multiThreadProcessImages(const OfxRectI& procWindow, const OfxPointD& rs) OVERRIDE FINAL
    {
        unused(rs);
        for (int y = procWindow.y1; y < procWindow.y2; y++) {
            if ( _effect.abort() ) {
                break;
            }

            PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);

            for (int x = procWindow.x1; x < procWindow.x2; x++) {
                const PIX *srcPix = (const PIX *)  (_srcImg ? _srcImg->getPixelAddress(x, y) : 0);

                // do we have a source image to scale up
                if (srcPix) {
                    for (int c = 0; c < nComponents; c++) {
#pragma message WARN("TODO")
                        dstPix[c] = max - srcPix[c];
                    }
                } else {
                    // no src pixel here, be black and transparent
                    for (int c = 0; c < nComponents; c++) {
                        dstPix[c] = 0;
                    }
                }

                // increment the dst pixel
                dstPix += nComponents;
            }
        }
    }
};

/** @brief The plugin that does our work */
class ReConvergePlugin
    : public ImageEffect
{
public:
    /** @brief ctor */
    ReConvergePlugin(OfxImageEffectHandle handle)
        : ImageEffect(handle)
        , _dstClip(NULL)
        , _srcClip(NULL)
        , _dispClip(NULL)
        , _convergepoint(NULL)
        , _offset(NULL)
        , _convergemode(NULL)
    {

        _dstClip = fetchClip(kOfxImageEffectOutputClipName);
        assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentAlpha ||
                             _dstClip->getPixelComponents() == ePixelComponentRGB ||
                             _dstClip->getPixelComponents() == ePixelComponentRGBA) );
        _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
        assert( (!_srcClip && getContext() == eContextGenerator) ||
                ( _srcClip && (!_srcClip->isConnected() || _srcClip->getPixelComponents() ==  ePixelComponentAlpha ||
                               _srcClip->getPixelComponents() == ePixelComponentRGB ||
                               _srcClip->getPixelComponents() == ePixelComponentRGBA) ) );
        _dispClip = getContext() == eContextFilter ? NULL : fetchClip(kClipDisparity);
        assert( !_dispClip || (_dispClip->getPixelComponents() == ePixelComponentAlpha || _dispClip->getPixelComponents() == ePixelComponentRGB || _dispClip->getPixelComponents() == ePixelComponentRGBA) );

        if (getContext() == eContextGeneral) {
            _convergepoint = fetchDouble2DParam(kParamConvergePoint);
            assert(_convergepoint);
        }
        _offset = fetchIntParam(kParamOffset);
        _convergemode = fetchChoiceParam(kParamConvergeMode);
        assert(_offset && _convergemode);
    }

private:
    /* Override the render */
    virtual void render(const RenderArguments &args) OVERRIDE FINAL;

    template <int nComponents>
    void renderInternal(const RenderArguments &args, BitDepthEnum dstBitDepth);

    // override the roi call
    virtual void getRegionsOfInterest(const RegionsOfInterestArguments &args, RegionOfInterestSetter &rois) OVERRIDE FINAL;

    /* set up and run a processor */
    void setupAndProcess(TranslateBase &, const RenderArguments &args);

private:
    // do not need to delete these, the ImageEffect is managing them for us
    Clip *_dstClip;
    Clip *_srcClip;
    Clip *_dispClip;
    Double2DParam *_convergepoint;
    IntParam     *_offset;
    ChoiceParam  *_convergemode;
};

/** @brief render for the filter */

// basic plugin render function, just a skelington to instantiate templates from

/* set up and run a processor */
void
ReConvergePlugin::setupAndProcess(TranslateBase &processor,
                                  const RenderArguments &args)
{
    // get a dst image
    auto_ptr<Image> dst( _dstClip->fetchImage(args.time) );

    if ( !dst.get() ) {
        throwSuiteStatusException(kOfxStatFailed);
    }
# ifndef NDEBUG
    BitDepthEnum dstBitDepth    = dst->getPixelDepth();
    PixelComponentEnum dstComponents  = dst->getPixelComponents();
    if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
         ( dstComponents != _dstClip->getPixelComponents() ) ) {
        setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
        throwSuiteStatusException(kOfxStatFailed);
    }
    checkBadRenderScaleOrField(dst, args);
# endif

    // fetch main input image
    auto_ptr<const Image> src( ( _srcClip && _srcClip->isConnected() ) ?
                                    _srcClip->fetchImage(args.time) : 0 );

# ifndef NDEBUG
    // make sure bit depths are sane
    if ( src.get() ) {
        checkBadRenderScaleOrField(src, args);
        BitDepthEnum srcBitDepth      = src->getPixelDepth();
        PixelComponentEnum srcComponents = src->getPixelComponents();

        // see if they have the same depths and bytes and all
        if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
            throwSuiteStatusException(kOfxStatErrImageFormat);
        }
    }
# endif

    int offset = _offset->getValueAtTime(args.time);
    int convergemode;
    _convergemode->getValueAtTime(args.time, convergemode);

    // set the images
    processor.setDstImg( dst.get() );
    processor.setSrcImg( src.get() );

    // set the render window
    processor.setRenderWindow(args.renderWindow, args.renderScale);

#pragma message WARN("TODO")
    (void)offset;
    // set the parameters
    if ( (getContext() == eContextGeneral) && _convergepoint && _dispClip ) {
        // fetch the disparity of the tracked point
    }
    switch (convergemode) {
    case 0:     // shift left
        break;
    case 1:     // shift right
        break;
    case 2:     // shift both
        break;
    }

    // Call the base class process member, this will call the derived templated process code
    processor.process();
} // ReConvergePlugin::setupAndProcess

// override the roi call
void
ReConvergePlugin::getRegionsOfInterest(const RegionsOfInterestArguments &args,
                                       RegionOfInterestSetter &rois)
{
    // set the ROI of the disp clip to the tracked point position (rounded to the nearest pixel)
    if ( (getContext() == eContextGeneral) && _convergepoint && _srcClip && _dispClip ) {
        OfxRectD roi;
        // since getRegionsOfInterest is not view-specific, return a full horizontal band
        roi = _srcClip->getRegionOfDefinition(args.time);
        if ( Coords::rectIsEmpty(roi) ) {
            return;
        }
        roi.y1 = args.regionOfInterest.y1;
        roi.y2 = args.regionOfInterest.y2;

        // TODO: we could compute a smaller area, depending on the convergence
        rois.setRegionOfInterest(*_dispClip, roi);
    }
}

// the internal render function
template <int nComponents>
void
ReConvergePlugin::renderInternal(const RenderArguments &args,
                                 BitDepthEnum dstBitDepth)
{
    switch (dstBitDepth) {
    case eBitDepthUByte: {
        ImageTranslator<unsigned char, nComponents, 255> fred(*this);
        setupAndProcess(fred, args);
        break;
    }
    case eBitDepthUShort: {
        ImageTranslator<unsigned short, nComponents, 65535> fred(*this);
        setupAndProcess(fred, args);
        break;
    }
    case eBitDepthFloat: {
        ImageTranslator<float, nComponents, 1> fred(*this);
        setupAndProcess(fred, args);
        break;
    }
    default:
        throwSuiteStatusException(kOfxStatErrUnsupported);
    }
}

// the overridden render function
void
ReConvergePlugin::render(const RenderArguments &args)
{
    // instantiate the render code based on the pixel depth of the dst clip
    BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
    PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();

    assert( kSupportsMultipleClipPARs   || !_srcClip || !_srcClip->isConnected() || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
    assert( kSupportsMultipleClipDepths || !_srcClip || !_srcClip->isConnected() || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
    // do the rendering
    if (dstComponents == ePixelComponentRGBA) {
        renderInternal<4>(args, dstBitDepth);
    } else if (dstComponents == ePixelComponentRGB) {
        renderInternal<3>(args, dstBitDepth);
#ifdef OFX_EXTENSIONS_NATRON
    } else if (dstComponents == ePixelComponentXY) {
        renderInternal<2>(args, dstBitDepth);
#endif
    } else {
        assert(dstComponents == ePixelComponentAlpha);
        renderInternal<1>(args, dstBitDepth);
    }
}

mDeclarePluginFactory(ReConvergePluginFactory, {ofxsThreadSuiteCheck();}, {});
struct ConvergePointParam
{
    static const char* name() { return kParamConvergePoint; }

    static const char* interactiveName() { return kParamInteractive; }
};

void
ReConvergePluginFactory::describe(ImageEffectDescriptor &desc)
{
    // basic labels
    desc.setLabel(kPluginName);
    desc.setPluginGrouping(kPluginGrouping);
    desc.setPluginDescription(kPluginDescription);

    // add the supported contexts
    desc.addSupportedContext(eContextFilter); // parameters are offset and convergemode
    desc.addSupportedContext(eContextGeneral); // adds second input for disparity (in the red channel), and convergepoint (with interact)

    // add supported pixel depths
    desc.addSupportedBitDepth(eBitDepthUByte);
    desc.addSupportedBitDepth(eBitDepthUShort);
    desc.addSupportedBitDepth(eBitDepthFloat);

    // set a few flags
    desc.setSingleInstance(false);
    desc.setHostFrameThreading(false);
    desc.setSupportsMultiResolution(kSupportsMultiResolution);
    desc.setSupportsTiles(kSupportsTiles);
    desc.setTemporalClipAccess(false);
    desc.setRenderTwiceAlways(false);
    desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
    desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);

    desc.setOverlayInteractDescriptor(new PositionOverlayDescriptor<ConvergePointParam>);
#ifdef OFX_EXTENSIONS_NATRON
    desc.setChannelSelector(ePixelComponentNone);
#endif
    desc.setRenderThreadSafety(kRenderThreadSafety);
}

void
ReConvergePluginFactory::describeInContext(ImageEffectDescriptor &desc,
                                           ContextEnum /*context*/)
{
    // Source clip only in the filter context
    // create the mandated source clip
    ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);

    srcClip->addSupportedComponent(ePixelComponentRGBA);
    srcClip->addSupportedComponent(ePixelComponentRGB);
#ifdef OFX_EXTENSIONS_NATRON
    srcClip->addSupportedComponent(ePixelComponentXY);
#endif
    srcClip->addSupportedComponent(ePixelComponentAlpha);
    srcClip->setTemporalClipAccess(false);
    srcClip->setSupportsTiles(kSupportsTiles);
    srcClip->setIsMask(false);

    // Optional disparity clip
    ClipDescriptor *dispClip = desc.defineClip(kClipDisparity);
    dispClip->addSupportedComponent(ePixelComponentRGBA);
    dispClip->addSupportedComponent(ePixelComponentRGB);
#ifdef OFX_EXTENSIONS_NATRON
    dispClip->addSupportedComponent(ePixelComponentXY);
#endif
    dispClip->addSupportedComponent(ePixelComponentAlpha);
    dispClip->setTemporalClipAccess(false);
    dispClip->setOptional(true);
    dispClip->setSupportsTiles(kSupportsTiles);

    // create the mandated output clip
    ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
    dstClip->addSupportedComponent(ePixelComponentRGBA);
    dstClip->addSupportedComponent(ePixelComponentRGB);
#ifdef OFX_EXTENSIONS_NATRON
    dstClip->addSupportedComponent(ePixelComponentXY);
#endif
    dstClip->addSupportedComponent(ePixelComponentAlpha);
    dstClip->setSupportsTiles(kSupportsTiles);

    // make some pages and to things in
    PageParamDescriptor *page = desc.definePageParam("Controls");

    // convergepoint
    {
        Double2DParamDescriptor *param = desc.defineDouble2DParam(kParamConvergePoint);
        param->setLabel(kParamConvergePointLabel);
        param->setHint(kParamConvergePointHint);
        param->setDoubleType(eDoubleTypeXYAbsolute);
        param->setDefaultCoordinateSystem(eCoordinatesNormalised);
        param->setDefault(0.5, 0.5);
        param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
        param->setDisplayRange(-10000, -10000, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
        param->setIncrement(1.);
        param->setAnimates(true);
        if (page) {
            page->addChild(*param);
        }
    }
    {
        BooleanParamDescriptor* param = desc.defineBooleanParam(kParamInteractive);
        param->setLabel(kParamInteractiveLabel);
        param->setHint(kParamInteractiveHint);
        param->setAnimates(false);
        if (page) {
            page->addChild(*param);
        }
    }

    // offset
    {
        IntParamDescriptor *param = desc.defineIntParam(kParamOffset);
        param->setLabel(kParamOffsetLabel);
        param->setHint(kParamOffsetHint);
        param->setDefault(0);
        param->setRange(-1000, 1000);
        param->setDisplayRange(-100, 100);
        param->setAnimates(true);
        if (page) {
            page->addChild(*param);
        }
    }

    // convergemode
    {
        ChoiceParamDescriptor *param = desc.defineChoiceParam(kParamConvergeMode);
        param->setLabel(kParamConvergeModeLabel);
        param->setHint(kParamConvergeModeHint);
        param->appendOption(kParamConvergeModeOptionShiftRight);
        param->appendOption(kParamConvergeModeOptionShiftLeft);
        param->appendOption(kParamConvergeModeOptionShiftBoth);
        param->setAnimates(true);
        if (page) {
            page->addChild(*param);
        }
    }
} // ReConvergePluginFactory::describeInContext

ImageEffect*
ReConvergePluginFactory::createInstance(OfxImageEffectHandle handle,
                                        ContextEnum /*context*/)
{
    return new ReConvergePlugin(handle);
}

static ReConvergePluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
mRegisterPluginFactoryInstance(p)

OFXS_NAMESPACE_ANONYMOUS_EXIT

#endif // ifdef DEBUG


char *
x509_proxy_email( const char *proxy_file )
{
	char *email = NULL;
	X509Credential* proxy_handle = x509_proxy_read( proxy_file );

	if ( proxy_handle == NULL ) {
		return NULL;
	}

	email = x509_proxy_email( proxy_handle );

	delete proxy_handle;

	return email;
}

/***************************************************************
 *
 *
 *
 ***************************************************************/

#include "condor_common.h"
#include "condor_config.h"
#include "condor_debug.h"
#include "util_lib_proto.h"
#include "condor_auth_ssl.h"
#include "DelegationInterface.h"
#include "subsystem_info.h"

#if defined(DLOPEN_GSI_LIBS) || defined(DLOPEN_VOMS_LIBS)
#include <dlfcn.h>
#endif

#include "globus_utils.h"

#include <openssl/x509v3.h>

static std::string _globus_error_message;

#if defined(HAVE_EXT_GLOBUS)

// This symbol is in libglobus_gssapi_gsi, but it's not exposed in any
extern gss_OID_desc *gss_nt_host_ip;

// Symbols from libglobus_common
int (*globus_module_activate_ptr)(
	globus_module_descriptor_t *) = NULL;
int (*globus_thread_set_model_ptr)(
	const char *) = NULL;
// Symbols from libglobus_gsi_credential
globus_result_t (*globus_gsi_cred_get_cert_ptr)(
	globus_gsi_cred_handle_t, X509 **) = NULL;
globus_result_t (*globus_gsi_cred_get_cert_chain_ptr)(
	globus_gsi_cred_handle_t, STACK_OF(X509) **) = NULL;
// Symbols from libglobus_gssapi_gsi
OM_uint32 (*gss_accept_sec_context_ptr)(
	OM_uint32 *, gss_ctx_id_t *, const gss_cred_id_t, const gss_buffer_t,
	const gss_channel_bindings_t, gss_name_t *, gss_OID *, gss_buffer_t,
	OM_uint32 *, OM_uint32 *, gss_cred_id_t *) = NULL;
OM_uint32 (*gss_compare_name_ptr)(
	OM_uint32 *, const gss_name_t, const gss_name_t, int *) = NULL;
OM_uint32 (*gss_context_time_ptr)(
	OM_uint32 *, const gss_ctx_id_t, OM_uint32 *) = NULL;
OM_uint32 (*gss_delete_sec_context_ptr)(
	OM_uint32 *, gss_ctx_id_t *, gss_buffer_t) = NULL;
OM_uint32 (*gss_display_name_ptr)(
	OM_uint32 *, const gss_name_t, gss_buffer_t, gss_OID *) = NULL;
OM_uint32 (*gss_import_name_ptr)(
	OM_uint32 *, const gss_buffer_t, const gss_OID, gss_name_t *) = NULL;
OM_uint32 (*gss_inquire_context_ptr)(
	OM_uint32 *, const gss_ctx_id_t, gss_name_t *, gss_name_t *,
	OM_uint32 *, gss_OID *, OM_uint32 *, int *, int *) = NULL;
OM_uint32 (*gss_release_buffer_ptr)(
	OM_uint32 *, gss_buffer_t) = NULL;
OM_uint32 (*gss_release_cred_ptr)(
	OM_uint32 *, gss_cred_id_t *) = NULL;
OM_uint32 (*gss_release_name_ptr)(
	OM_uint32 *, gss_name_t *) = NULL;
OM_uint32 (*gss_unwrap_ptr)(
	OM_uint32 *, const gss_ctx_id_t, const gss_buffer_t, gss_buffer_t, int *,
	gss_qop_t *) = NULL;
OM_uint32 (*gss_wrap_ptr)(
	OM_uint32 *, const gss_ctx_id_t, int, gss_qop_t, const gss_buffer_t,
	int *, gss_buffer_t) = NULL;
gss_OID_desc **gss_nt_host_ip_ptr = NULL;
// Symbols from libglobus_gss_assist
OM_uint32 (*globus_gss_assist_display_status_str_ptr)(
	char **, char *, OM_uint32, OM_uint32, int) = NULL;
globus_result_t (*globus_gss_assist_map_and_authorize_ptr)(
	gss_ctx_id_t, char *, char *, char *, unsigned int) = NULL;
OM_uint32 (*globus_gss_assist_acquire_cred_ptr)(
	OM_uint32 *, gss_cred_usage_t, gss_cred_id_t *) = NULL;
OM_uint32 (*globus_gss_assist_init_sec_context_ptr)(
	OM_uint32 *, const gss_cred_id_t, gss_ctx_id_t *, char *, OM_uint32,
	OM_uint32 *, int *, int (*)(void *, void **, size_t *), void *,
	int (*)(void *, void *, size_t), void *) = NULL;
globus_module_descriptor_t *globus_i_gsi_gss_assist_module_ptr = NULL;
#endif /* defined(HAVE_EXT_GLOBUS) */

// Symbols from libvomsapi
#if defined(HAVE_EXT_VOMS)
void (*VOMS_Destroy_ptr)(
	struct vomsdata *) = NULL;
char *(*VOMS_ErrorMessage_ptr)(
	struct vomsdata *, int, char *, int) = NULL;
struct vomsdata *(*VOMS_Init_ptr)(
	char *, char *) = NULL;
int (*VOMS_Retrieve_ptr)(
	X509 *, STACK_OF(X509) *, int, struct vomsdata *, int *) = NULL;
int (*VOMS_SetVerificationType_ptr)(
	int, struct vomsdata *, int *) = NULL;
#endif /* defined(HAVE_EXT_VOMS) */

#define NOT_SUPPORTED_MSG "This version of Condor doesn't support GSI security"

void
warn_on_gsi_usage()
{
	static time_t last_warn = 0;
	time_t now = time(NULL);
	if ( now < last_warn + (12*60*60) ) {
		return;
	}
	last_warn = now;
	if ( ! param_boolean("WARN_ON_GSI_USAGE", true) ) {
		return;
	}
	SubsystemInfo* my_subsys = get_mySubSystem();
	SubsystemType subsys_type = my_subsys ? my_subsys->getType() : SUBSYSTEM_TYPE_MIN;
	if ( subsys_type == SUBSYSTEM_TYPE_TOOL || subsys_type == SUBSYSTEM_TYPE_SUBMIT ) {
		fprintf(stderr, "WARNING: GSI authentication is being attempted! GSI will not work in future releases.\n");
		fprintf(stderr, "For details, see http://example.com\n");
	} else {
		dprintf(D_ALWAYS, "WARNING: GSI authentication is being attempted! GSI will not work in future releases. (Will warn again after 12 hours)\n");
		dprintf(D_ALWAYS, "For details, see https://htcondor-wiki.cs.wisc.edu/index.cgi/wiki?p=PlanToReplaceGridCommunityToolkit\n");
	}
}

void
warn_on_gsi_config()
{
	static time_t last_warn = 0;
	time_t now = time(NULL);
	if ( now < last_warn + (12*60*60) ) {
		return;
	}
	last_warn = now;
	if ( ! param_boolean("WARN_ON_GSI_CONFIGURATION", true) ) {
		return;
	}
	SubsystemInfo* my_subsys = get_mySubSystem();
	SubsystemType subsys_type = my_subsys ? my_subsys->getType() : SUBSYSTEM_TYPE_MIN;
	if ( subsys_type == SUBSYSTEM_TYPE_TOOL || subsys_type == SUBSYSTEM_TYPE_SUBMIT ) {
		fprintf(stderr, "WARNING: GSI authentication is enabled by your security configuration! GSI will not work in future releases.\n");
		fprintf(stderr, "For details, see https://htcondor-wiki.cs.wisc.edu/index.cgi/wiki?p=PlanToReplaceGridCommunityToolkit\n");
	} else {
		dprintf(D_ALWAYS, "WARNING: GSI authentication is is enabled by your security configuration! GSI will not work in future releases. (Will warn again after 12 hours)\n");
		dprintf(D_ALWAYS, "For details, see https://htcondor-wiki.cs.wisc.edu/index.cgi/wiki?p=PlanToReplaceGridCommunityToolkit\n");
	}
}

const char *
x509_error_string( void )
{
	return _globus_error_message.c_str();
}

static
void
set_error_string( const char *message )
{
	_globus_error_message = message;
}

/* Activate the globus gsi modules for use by functions in this file.
 * Returns zero if the modules were successfully activated. Returns -1 if
 * something went wrong.
 */

int
activate_globus_gsi( void )
{
#if !defined(HAVE_EXT_GLOBUS)
	set_error_string( NOT_SUPPORTED_MSG );
	return -1;
#else
	static bool globus_gsi_activated = false;
	static bool activation_failed = false;

	if ( globus_gsi_activated ) {
		return 0;
	}
	if ( activation_failed ) {
		return -1;
	}

	if ( Condor_Auth_SSL::Initialize() == false ) {
		// Error in the dlopen/sym calls for libssl, return failure.
		_globus_error_message = "Failed to open SSL library";
		activation_failed = true;
		return -1;
	}

#if defined(DLOPEN_GSI_LIBS)
	void *dl_hdl;

	if ( (dl_hdl = dlopen(LIBLTDL_SO, RTLD_LAZY)) == NULL ||
		 (dl_hdl = dlopen(LIBGLOBUS_COMMON_SO, RTLD_LAZY)) == NULL ||
		 !(globus_module_activate_ptr = (int (*)(globus_module_descriptor_t*))dlsym(dl_hdl, "globus_module_activate")) ||
		 !(globus_thread_set_model_ptr = (int (*)(const char*))dlsym(dl_hdl, "globus_thread_set_model")) ||
		 (dl_hdl = dlopen(LIBGLOBUS_CALLOUT_SO, RTLD_LAZY)) == NULL ||
		 (dl_hdl = dlopen(LIBGLOBUS_PROXY_SSL_SO, RTLD_LAZY)) == NULL ||
		 (dl_hdl = dlopen(LIBGLOBUS_OPENSSL_ERROR_SO, RTLD_LAZY)) == NULL ||
		 (dl_hdl = dlopen(LIBGLOBUS_OPENSSL_SO, RTLD_LAZY)) == NULL ||
		 (dl_hdl = dlopen(LIBGLOBUS_GSI_CERT_UTILS_SO, RTLD_LAZY)) == NULL ||
		 (dl_hdl = dlopen(LIBGLOBUS_GSI_SYSCONFIG_SO, RTLD_LAZY)) == NULL ||
		 (dl_hdl = dlopen(LIBGLOBUS_OLDGAA_SO, RTLD_LAZY)) == NULL ||
		 (dl_hdl = dlopen(LIBGLOBUS_GSI_CALLBACK_SO, RTLD_LAZY)) == NULL ||
		 (dl_hdl = dlopen(LIBGLOBUS_GSI_CREDENTIAL_SO, RTLD_LAZY))== NULL ||
		 !(globus_gsi_cred_get_cert_ptr = (globus_result_t (*)(globus_l_gsi_cred_handle_s*, X509**))dlsym(dl_hdl, "globus_gsi_cred_get_cert")) ||
		 !(globus_gsi_cred_get_cert_chain_ptr = (globus_result_t (*)(globus_gsi_cred_handle_t, STACK_OF(X509)**))dlsym(dl_hdl, "globus_gsi_cred_get_cert_chain")) ||
		 (dl_hdl = dlopen(LIBGLOBUS_GSI_PROXY_CORE_SO, RTLD_LAZY)) == NULL ||
		 (dl_hdl = dlopen(LIBGLOBUS_GSSAPI_GSI_SO, RTLD_LAZY)) == NULL ||
		 !(gss_accept_sec_context_ptr = (OM_uint32 (*)(OM_uint32 *, gss_ctx_id_t *, const gss_cred_id_t, const gss_buffer_t, const gss_channel_bindings_t, gss_name_t *, gss_OID *, gss_buffer_t, OM_uint32 *, OM_uint32 *, gss_cred_id_t *))dlsym(dl_hdl, "gss_accept_sec_context")) ||
		 !(gss_compare_name_ptr = (OM_uint32 (*)(OM_uint32*, const gss_name_t, const gss_name_t, int*))dlsym(dl_hdl, "gss_compare_name")) ||
		 !(gss_context_time_ptr = (OM_uint32 (*)(OM_uint32*, const gss_ctx_id_t, OM_uint32*))dlsym(dl_hdl, "gss_context_time")) ||
		 !(gss_delete_sec_context_ptr = (OM_uint32 (*)(OM_uint32*, gss_ctx_id_t*, gss_buffer_t))dlsym(dl_hdl, "gss_delete_sec_context")) ||
		 !(gss_display_name_ptr = (OM_uint32 (*)( OM_uint32*, const gss_name_t, gss_buffer_t, gss_OID*))dlsym(dl_hdl, "gss_display_name")) ||
		 !(gss_import_name_ptr = (OM_uint32 (*)(OM_uint32*, const gss_buffer_t, const gss_OID, gss_name_t*))dlsym(dl_hdl, "gss_import_name")) ||
		 !(gss_inquire_context_ptr = (OM_uint32 (*)(OM_uint32*, const gss_ctx_id_t, gss_name_t*, gss_name_t*, OM_uint32*, gss_OID*, OM_uint32*, int*, int*))dlsym(dl_hdl, "gss_inquire_context")) ||
		 !(gss_release_buffer_ptr = (OM_uint32 (*)(OM_uint32*, gss_buffer_t))dlsym(dl_hdl, "gss_release_buffer")) ||
		 !(gss_release_cred_ptr = (OM_uint32 (*)(OM_uint32*, gss_cred_id_desc_struct**))dlsym(dl_hdl, "gss_release_cred")) ||
		 !(gss_release_name_ptr = (OM_uint32 (*)(OM_uint32*, gss_name_t*))dlsym(dl_hdl, "gss_release_name")) ||
		 !(gss_unwrap_ptr = (OM_uint32 (*)(OM_uint32*, const gss_ctx_id_t, const gss_buffer_t, gss_buffer_t, int*, gss_qop_t*))dlsym(dl_hdl, "gss_unwrap")) ||
		 !(gss_wrap_ptr = (OM_uint32 (*)(OM_uint32*, const gss_ctx_id_t, int, gss_qop_t, const gss_buffer_t, int*, gss_buffer_t))dlsym(dl_hdl, "gss_wrap")) ||
		 !(gss_nt_host_ip_ptr = (gss_OID_desc **)dlsym(dl_hdl, "gss_nt_host_ip")) ||
		 (dl_hdl = dlopen(LIBGLOBUS_GSS_ASSIST_SO, RTLD_LAZY)) == NULL ||
		 !(globus_gss_assist_display_status_str_ptr = (OM_uint32 (*)(char**, char*, OM_uint32, OM_uint32, int))dlsym(dl_hdl, "globus_gss_assist_display_status_str")) ||
		 !(globus_gss_assist_map_and_authorize_ptr = (globus_result_t (*)(gss_ctx_id_t, char*, char*, char*, unsigned int))dlsym(dl_hdl, "globus_gss_assist_map_and_authorize")) ||
		 !(globus_gss_assist_acquire_cred_ptr = (OM_uint32 (*)(OM_uint32*, gss_cred_usage_t, gss_cred_id_t*))dlsym(dl_hdl, "globus_gss_assist_acquire_cred")) ||
		 !(globus_gss_assist_init_sec_context_ptr = (OM_uint32 (*)(OM_uint32*, const gss_cred_id_t, gss_ctx_id_t*, char*, OM_uint32, OM_uint32*, int*, int (*)(void*, void**, size_t*), void*, int (*)(void*, void*, size_t), void*))dlsym(dl_hdl, "globus_gss_assist_init_sec_context")) ||
		 !(globus_i_gsi_gss_assist_module_ptr = (globus_module_descriptor_t*)dlsym(dl_hdl, "globus_i_gsi_gss_assist_module"))
		 ) {
			 // Error in the dlopen/sym calls, return failure.
		const char *err = dlerror();
		formatstr( _globus_error_message, "Failed to open GSI libraries: %s", err ? err : "Unknown error" );
		activation_failed = true;
		return -1;
	}
#else
	globus_module_activate_ptr = globus_module_activate;
	globus_thread_set_model_ptr = globus_thread_set_model;
	globus_gsi_cred_get_cert_ptr = globus_gsi_cred_get_cert;
	globus_gsi_cred_get_cert_chain_ptr = globus_gsi_cred_get_cert_chain;
	gss_accept_sec_context_ptr = gss_accept_sec_context;
	gss_compare_name_ptr = gss_compare_name;
	gss_context_time_ptr = gss_context_time;
	gss_delete_sec_context_ptr = gss_delete_sec_context;
	gss_display_name_ptr = gss_display_name;
	gss_import_name_ptr = gss_import_name;
	gss_inquire_context_ptr = gss_inquire_context;
	gss_release_buffer_ptr = gss_release_buffer;
	gss_release_cred_ptr = gss_release_cred;
	gss_release_name_ptr = gss_release_name;
	gss_unwrap_ptr = gss_unwrap;
	gss_wrap_ptr = gss_wrap;
	gss_nt_host_ip_ptr = &gss_nt_host_ip;
	globus_gss_assist_display_status_str_ptr = globus_gss_assist_display_status_str;
	globus_gss_assist_map_and_authorize_ptr = globus_gss_assist_map_and_authorize;
	globus_gss_assist_acquire_cred_ptr = globus_gss_assist_acquire_cred;
	globus_gss_assist_init_sec_context_ptr = globus_gss_assist_init_sec_context;
	globus_i_gsi_gss_assist_module_ptr = &globus_i_gsi_gss_assist_module;
#endif

	// If this fails, it means something already configured a threaded
	// model. That won't harm us, so ignore it.
	(*globus_thread_set_model_ptr)( GLOBUS_THREAD_MODEL_NONE );

	if ( (*globus_module_activate_ptr)(globus_i_gsi_gss_assist_module_ptr) ) {
		set_error_string( "couldn't activate globus gsi gss assist module" );
		activation_failed = true;
		return -1;
	}

	globus_gsi_activated = true;
	return 0;
#endif
}

/* Return the path to the X509 proxy file as determined by GSI/SSL.
 * Returns NULL if the filename can't be determined. Otherwise, the
 * string returned must be freed with free().
 */
char *
get_x509_proxy_filename( void )
{
	char *proxy_file = getenv("X509_USER_PROXY");
	if ( proxy_file == nullptr ) {
#if !defined(WIN32)
		std::string tmp_file;
		formatstr(tmp_file, "/tmp/x509up_u%d", geteuid());
		proxy_file = strdup(tmp_file.c_str());
#endif
	} else {
		proxy_file = strdup(proxy_file);
	}
	return proxy_file;
}

#if defined(HAVE_EXT_VOMS)
static
int
initialize_voms()
{
#if !defined(HAVE_EXT_VOMS)
	set_error_string( NOT_SUPPORTED_MSG );
	return -1;
#else
	static bool voms_initialized = false;
	static bool initialization_failed = false;

	if ( voms_initialized ) {
		return 0;
	}
	if ( initialization_failed ) {
		return -1;
	}

	if ( Condor_Auth_SSL::Initialize() == false ) {
		// Error in the dlopen/sym calls for libssl, return failure.
		_globus_error_message = "Failed to open SSL library";
		initialization_failed = true;
		return -1;
	}

#if defined(DLOPEN_VOMS_LIBS)
	void *dl_hdl;

	if ( (dl_hdl = dlopen(LIBVOMSAPI_SO, RTLD_LAZY)) == NULL ||
		 !(VOMS_Destroy_ptr = (void (*)(vomsdata*))dlsym(dl_hdl, "VOMS_Destroy")) ||
		 !(VOMS_ErrorMessage_ptr = (char* (*)(vomsdata*, int, char*, int))dlsym(dl_hdl, "VOMS_ErrorMessage")) ||
		 !(VOMS_Init_ptr = (vomsdata* (*)(char*, char*))dlsym(dl_hdl, "VOMS_Init")) ||
		 !(VOMS_Retrieve_ptr = (int (*)(X509*, STACK_OF(X509)*, int, struct vomsdata*, int*))dlsym(dl_hdl, "VOMS_Retrieve")) ||
		 !(VOMS_SetVerificationType_ptr = (int (*)(int, vomsdata*, int*))dlsym(dl_hdl, "VOMS_SetVerificationType"))
		 ) {
			 // Error in the dlopen/sym calls, return failure.
		const char *err = dlerror();
		formatstr( _globus_error_message, "Failed to open VOMS library: %s", err ? err : "Unknown error" );
		initialization_failed = true;
		return -1;
	}
#else
	VOMS_Destroy_ptr = VOMS_Destroy;
	VOMS_ErrorMessage_ptr = VOMS_ErrorMessage;
	VOMS_Init_ptr = VOMS_Init;
	VOMS_Retrieve_ptr = VOMS_Retrieve;
	VOMS_SetVerificationType_ptr = VOMS_SetVerificationType;
#endif

	voms_initialized = true;
	return 0;
#endif
}

// caller must free result
static
char* trim_quotes( char* instr ) {
	char * result;
	int  instr_len;

	if (instr == NULL) {
		return NULL;
	}

	instr_len = strlen(instr);
	// to trim, must be minimum three characters with a double quote first and last
	if ((instr_len > 2) && (instr[0] == '"') && (instr[instr_len-1]) == '"') {
		// alloc a shorter buffer, copy everything in, and null terminate
		result = (char*)malloc(instr_len - 1); // minus two quotes, plus one NULL terminator.
		strncpy(result, &(instr[1]), instr_len-2);
		result[instr_len-2] = 0;
	} else {
		result = strdup(instr);
	}

	return result;
}

// caller responsible for freeing
static
char*
quote_x509_string( char* instr) {
	char * result_string = 0;
	int    result_string_len = 0;

	char * x509_fqan_escape = 0;
	char * x509_fqan_escape_sub = 0;
	char * x509_fqan_delimiter = 0;
	char * x509_fqan_delimiter_sub = 0;

	int x509_fqan_escape_sub_len = 0;
	int x509_fqan_delimiter_sub_len = 0;

	char * tmp_scan_ptr;

	// NULL in, NULL out
	if (!instr) {
		return NULL;
	}

	// we look at first char only.  default '&'.
    if (!(x509_fqan_escape = param("X509_FQAN_ESCAPE"))) {
		x509_fqan_escape = strdup("&");
	}
	// can be multiple chars
    if (!(x509_fqan_escape_sub = param("X509_FQAN_ESCAPE_SUB"))) {
		x509_fqan_escape_sub = strdup("&");
	}

	// we look at first char only.  default ','.
    if (!(x509_fqan_delimiter = param("X509_FQAN_DELIMITER"))) {
		x509_fqan_delimiter = strdup(",");
	}
	// can be multiple chars
    if (!(x509_fqan_delimiter_sub = param("X509_FQAN_DELIMITER_SUB"))) {
		x509_fqan_delimiter_sub = strdup(",");
	}

	// phase 0, trim quotes off if needed
	// use tmp_scan_ptr to temporarily hold trimmed strings while being reassigned.
	tmp_scan_ptr = trim_quotes(x509_fqan_escape);
	free (x509_fqan_escape);
	x509_fqan_escape = tmp_scan_ptr;

	tmp_scan_ptr = trim_quotes(x509_fqan_escape_sub);
	free (x509_fqan_escape_sub);
	x509_fqan_escape_sub = tmp_scan_ptr;
	x509_fqan_escape_sub_len = strlen(x509_fqan_escape_sub);

	tmp_scan_ptr = trim_quotes(x509_fqan_delimiter);
	free (x509_fqan_delimiter);
	x509_fqan_delimiter = tmp_scan_ptr;

	tmp_scan_ptr = trim_quotes(x509_fqan_delimiter_sub);
	free (x509_fqan_delimiter_sub);
	x509_fqan_delimiter_sub = tmp_scan_ptr;
	x509_fqan_delimiter_sub_len = strlen(x509_fqan_delimiter_sub);

	// phase 1, scan the string to compute the new length
	result_string_len = 0;
	for (tmp_scan_ptr = instr; *tmp_scan_ptr; tmp_scan_ptr++) {
		if( (*tmp_scan_ptr)==x509_fqan_escape[0] ) {
			result_string_len += x509_fqan_escape_sub_len;
		} else if( (*tmp_scan_ptr)==x509_fqan_delimiter[0] ) {
			result_string_len += x509_fqan_delimiter_sub_len;
		} else {
			result_string_len++;
		}
	}

	// phase 2, process the string into the result buffer

	// malloc new string (with NULL terminator)
	result_string = (char*) malloc (result_string_len + 1);
	ASSERT( result_string );
	*result_string = 0;
	result_string_len = 0;

	for (tmp_scan_ptr = instr; *tmp_scan_ptr; tmp_scan_ptr++) {
		if( (*tmp_scan_ptr)==x509_fqan_escape[0] ) {
			strcat(&(result_string[result_string_len]), x509_fqan_escape_sub);
			result_string_len += x509_fqan_escape_sub_len;
		} else if( (*tmp_scan_ptr)==x509_fqan_delimiter[0] ) {
			strcat(&(result_string[result_string_len]), x509_fqan_delimiter_sub);
			result_string_len += x509_fqan_delimiter_sub_len;
		} else {
			result_string[result_string_len] = *tmp_scan_ptr;
			result_string_len++;
		}
		result_string[result_string_len] = 0;
	}

	// clean up
	free(x509_fqan_escape);
	free(x509_fqan_escape_sub);
	free(x509_fqan_delimiter);
	free(x509_fqan_delimiter_sub);

	return result_string;
}
#endif /* defined(HAVE_EXT_VOMS) */

X509Credential* x509_proxy_read( const char *proxy_file )
{
	X509Credential *cred = nullptr;
	char *my_proxy_file = NULL;
	bool error = false;

	/* Check for proxy file */
	if (proxy_file == NULL) {
		my_proxy_file = get_x509_proxy_filename();
		if (my_proxy_file == NULL) {
			goto cleanup;
		}
		proxy_file = my_proxy_file;
	}

	// We should have a proxy file, now, try to read it
	cred = new X509Credential(proxy_file, "");
	if (! cred->GetCert()) {
		set_error_string( "unable to read proxy file" );
		error = true;
		goto cleanup;
	}

 cleanup:
	if (my_proxy_file) {
		free(my_proxy_file);
	}

	if (error && cred) {
		delete cred;
		cred = nullptr;
	}

	return cred;
}

// This is a slightly modified verson of globus_gsi_cert_utils_make_time()
// from the Grid Community Toolkit. It turns an ASN1_UTCTIME into a time_t.
// libressl and older openssl don't provide a way to do this conversion.
#if defined(LIBRESSL_VERSION_NUMBER)
static
time_t my_globus_gsi_cert_utils_make_time(const ASN1_UTCTIME *ctm)
{
	char *str;
	time_t offset;
	char buff1[24];
	char *p;
	int i;
	struct tm tm;
	time_t newtime = -1;

	p = buff1;
	i = ctm->length;
	str = (char *)ctm->data;
	if ((i < 11) || (i > 17)) {
		newtime = 0;
	}
	memcpy(p,str,10);
	p += 10;
	str += 10;

	if ((*str == 'Z') || (*str == '-') || (*str == '+')) {
		*(p++)='0'; *(p++)='0';
	} else {
		*(p++)= *(str++); *(p++)= *(str++);
	}
	*(p++)='Z';
	*(p++)='\0';

	if (*str == 'Z') {
		offset=0;
	} else {
		if ((*str != '+') && (str[5] != '-')) {
			newtime = 0;
		}
		offset=((str[1]-'0')*10+(str[2]-'0'))*60;
		offset+=(str[3]-'0')*10+(str[4]-'0');
		if (*str == '-') {
			offset=-offset;
		}
	}

	tm.tm_isdst = 0;
	tm.tm_year = (buff1[0]-'0')*10+(buff1[1]-'0');

	if (tm.tm_year < 70) {
		tm.tm_year+=100;
	}

	tm.tm_mon   = (buff1[2]-'0')*10+(buff1[3]-'0')-1;
	tm.tm_mday  = (buff1[4]-'0')*10+(buff1[5]-'0');
	tm.tm_hour  = (buff1[6]-'0')*10+(buff1[7]-'0');
	tm.tm_min   = (buff1[8]-'0')*10+(buff1[9]-'0');
	tm.tm_sec   = (buff1[10]-'0')*10+(buff1[11]-'0');

	newtime = (timegm(&tm) + offset*60*60);

	return newtime;
}
#endif

time_t x509_proxy_expiration_time( X509 *cert, STACK_OF(X509)* chain )
{
	time_t expiration_time = -1;
	X509 *curr_cert = cert;
	int cert_cnt = 0;
	if ( chain ) {
		cert_cnt = sk_X509_num(chain);
	}

	while ( curr_cert ) {
		time_t curr_expire = 0;
#if !defined(LIBRESSL_VERSION_NUMBER)
		int diff_days = 0;
		int diff_secs = 0;
		if ( ! ASN1_TIME_diff(&diff_days, &diff_secs, NULL, X509_get_notAfter(curr_cert)) ) {
			_globus_error_message = "Failed to calculate expration time";
			expiration_time = -1;
			break;
		} else {
			curr_expire = time(NULL) + diff_secs + 24*3600*diff_days;
		}
#else
		curr_expire = my_globus_gsi_cert_utils_make_time(X509_get_notAfter(curr_cert));
#endif
		if ( expiration_time == -1 || curr_expire < expiration_time ) {
			expiration_time = curr_expire;
		}
		if ( chain && cert_cnt ) {
			cert_cnt--;
			curr_cert = sk_X509_value(chain, cert_cnt);
		} else {
			curr_cert = NULL;
		}
	}
	return expiration_time;
}

time_t x509_proxy_expiration_time(X509Credential* cred)
{
	return x509_proxy_expiration_time(cred->GetCert(), cred->GetChain());
}

char* x509_proxy_email( X509 * /*cert*/, STACK_OF(X509)* cert_chain )
{
	X509_NAME *email_orig = NULL;
	GENERAL_NAME *gen;
	GENERAL_NAMES *gens;
	X509 *tmp_cert = NULL;
	char *email = NULL, *email2 = NULL;
	int i, j;

	// TODO We ignore the end-point certificate. This is fine for the
	//   usual case with a proxy certificate, but is wrong.
	for(i = 0; i < sk_X509_num(cert_chain) && email == NULL; ++i) {
		if((tmp_cert = sk_X509_value(cert_chain, i)) == NULL) {
			continue;
		}
		if ((email_orig = (X509_NAME *)X509_get_ext_d2i(tmp_cert, NID_pkcs9_emailAddress, 0, 0)) != NULL) {
			if ((email2 = X509_NAME_oneline(email_orig, NULL, 0)) == NULL) {
				continue;
			} else {
				// Return something that we can free().
				email = strdup(email2);
				OPENSSL_free(email2);
				break;
			}
		}
		gens = (GENERAL_NAMES *)X509_get_ext_d2i(tmp_cert, NID_subject_alt_name, 0, 0);
		if (gens) {
			for (j = 0; j < sk_GENERAL_NAME_num(gens); ++j) {
				if ((gen = sk_GENERAL_NAME_value(gens, j)) == NULL) {
					continue;
				}
				if (gen->type != GEN_EMAIL) {
					continue;
				}
				ASN1_IA5STRING *email_ia5 = gen->d.ia5;
				// Sanity checks.
				if (email_ia5->type != V_ASN1_IA5STRING) goto cleanup;
				if (!email_ia5->data || !email_ia5->length) goto cleanup;
				email2 = BUF_strdup((char *)email_ia5->data);
				// We want to return something we can free(), so make another copy.
				if (email2) {
					email = strdup(email2);
					OPENSSL_free(email2);
				}
				break;
			}
			sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
		}
	}

	if (email == NULL) {
		set_error_string( "unable to extract email" );
		goto cleanup;
	}

 cleanup:
	if (email_orig) {
		X509_NAME_free(email_orig);
	}

	return email;
}

char* x509_proxy_email(X509Credential* cred)
{
	return x509_proxy_email(cred->GetCert(), cred->GetChain());
}

char* x509_proxy_subject_name(X509* cert)
{
	char *subject_name = NULL;

	char* buf = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
	if(buf) {
		// Return something that we can free().
		subject_name = strdup(buf);
		OPENSSL_free(buf);
	} else {
		set_error_string( "unable to extract subject name" );
	}

	return subject_name;
}

char* x509_proxy_subject_name(X509Credential* cred)
{
	return x509_proxy_subject_name(cred->GetCert());
}

char* x509_proxy_identity_name(X509 *cert, STACK_OF(X509)* cert_chain)
{
	X509* target_cert = NULL;
	X509* tmp_cert = NULL;

	if (X509_get_ext_by_NID(cert, NID_proxyCertInfo, -1) < 0) {
		target_cert = cert;
	}

	for (int i = 0; i < sk_X509_num(cert_chain) && target_cert == NULL; ++i) {
		if ((tmp_cert = sk_X509_value(cert_chain, i)) == NULL) {
			continue;
		}
		if (X509_get_ext_by_NID(tmp_cert, NID_proxyCertInfo, -1) < 0) {
			target_cert = tmp_cert;
		}
	}

	if (target_cert) {
		return x509_proxy_subject_name(target_cert);
	}

	set_error_string( "unable to extract identity name" );
	return NULL;
}

char* x509_proxy_identity_name(X509Credential* cred)
{
	return x509_proxy_identity_name(cred->GetCert(), cred->GetChain());
}

#if !defined(HAVE_EXT_VOMS)
int
extract_VOMS_info( X509*, STACK_OF(X509)*, int, char **, char **, char **)
{
	return 1;
}
#else
int
extract_VOMS_info( X509 *cert, STACK_OF(X509) *chain, int verify_type, char **voname, char **firstfqan, char **quoted_DN_and_FQAN)
{

	int ret;
	struct vomsdata *voms_data = NULL;
	struct voms *voms_cert  = NULL;
	char *subject_name = NULL;
	char **fqan = NULL;
	int voms_err;
	int fqan_len = 0;
	char *retfqan = NULL;
	char *tmp_scan_ptr = NULL;

	char* x509_fqan_delimiter = NULL;

	if ( initialize_voms() != 0 ) {
		return 1;
	}

	// calling this function on something that doesn't have VOMS attributes
	// should return error 1.  when the config knob disables VOMS, behave the
	// same way.
	if (!param_boolean("USE_VOMS_ATTRIBUTES", true)) {
		return 1;
	}

	subject_name = x509_proxy_identity_name(cert, chain);
	if (subject_name == NULL) {
		set_error_string( "unable to extract subject name" );
		ret = 12;
		goto end;
	}

	voms_data = (*VOMS_Init_ptr)(NULL, NULL);
	if (voms_data == NULL) {
		ret = 13;
		goto end;
	}

	if (verify_type == 0) {
		ret = (*VOMS_SetVerificationType_ptr)( VERIFY_NONE, voms_data, &voms_err );
		if (ret == 0) {
			(*VOMS_ErrorMessage_ptr)(voms_data, voms_err, NULL, 0);
			ret = voms_err;
			goto end;
		}
	}

	ret = (*VOMS_Retrieve_ptr)(cert, chain, RECURSE_CHAIN,
						voms_data, &voms_err);

	// If verification was requested and no extensions were returned,
	// try again without verification. If we get extensions that time,
	// then verification failed. In that case, issue a warning, then
	// act as if there were no extensions.
	if (ret == 0 && verify_type != 0 ) {
		ret = (*VOMS_SetVerificationType_ptr)( VERIFY_NONE, voms_data, &voms_err );
		if (ret == 0) {
			(*VOMS_ErrorMessage_ptr)(voms_data, voms_err, NULL, 0);
			ret = voms_err;
			goto end;
		}

		ret = (*VOMS_Retrieve_ptr)(cert, chain, RECURSE_CHAIN,
						voms_data, &voms_err);
		if (ret != 0) {
			dprintf(D_ALWAYS, "WARNING! X.509 certificate '%s' has VOMS "
					"extensions that can't be verified. Ignoring them. "
					"(To silence this warning, set USE_VOMS_ATTRIBUTES=False)\n",
					subject_name);
		}
		// Report no (verified) VOMS extensions
		ret = 1;
		goto end;
	}
	if (ret == 0) {
		if (voms_err == VERR_NOEXT) {
			// No VOMS extensions present
			ret = 1;
			goto end;
		} else {
			(*VOMS_ErrorMessage_ptr)(voms_data, voms_err, NULL, 0);
			ret = voms_err;
			goto end;
		}
	}

	// we only support one cert for now.  serializing and encoding all the
	// attributes is bad enough, i don't want to deal with doing this to
	// multiple certs.
	voms_cert = voms_data->data[0];

	if (voms_cert == NULL) {
		// No VOMS certs?? Treat like VOMS_Retrieve() returned VERR_NOEXT.
		ret = 1;
		goto end;
	}

	// fill in the unquoted versions of things
	if(voname) {
		*voname = strdup(voms_cert->voname ? voms_cert->voname : "");
	}

	if(firstfqan) {
		*firstfqan = strdup(voms_cert->fqan[0] ? voms_cert->fqan[0] : "");
	}

	// only construct the quoted_DN_and_FQAN if needed
	if (quoted_DN_and_FQAN) {
		// get our delimiter and trim it
		if (!(x509_fqan_delimiter = param("X509_FQAN_DELIMITER"))) {
			x509_fqan_delimiter = strdup(",");
		}
		tmp_scan_ptr = trim_quotes(x509_fqan_delimiter);
		free(x509_fqan_delimiter);
		x509_fqan_delimiter = tmp_scan_ptr;

		// calculate the length
		fqan_len = 0;

		// start with the length of the quoted DN
		tmp_scan_ptr = quote_x509_string( subject_name );
		fqan_len += strlen( tmp_scan_ptr );
		free(tmp_scan_ptr);

		// add the length of delimiter plus each voms attribute
		for (fqan = voms_cert->fqan; fqan && *fqan; fqan++) {
			// delimiter
			fqan_len += strlen(x509_fqan_delimiter);

			tmp_scan_ptr = quote_x509_string( *fqan );
			fqan_len += strlen( tmp_scan_ptr );
			free(tmp_scan_ptr);
		}

		// now malloc enough room for the quoted DN, quoted attrs, delimiters, and
		// NULL terminator
		retfqan = (char*) malloc (fqan_len+1);
		*retfqan = 0;  // set null terminiator

		// reset length counter -- we use this for efficient appending.
		fqan_len = 0;

		// start with the quoted DN
		tmp_scan_ptr = quote_x509_string( subject_name );
		strcat(retfqan, tmp_scan_ptr);
		fqan_len += strlen( tmp_scan_ptr );
		free(tmp_scan_ptr);

		// add the delimiter plus each voms attribute
		for (fqan = voms_cert->fqan; fqan && *fqan; fqan++) {
			// delimiter
			strcat(&(retfqan[fqan_len]), x509_fqan_delimiter);
			fqan_len += strlen(x509_fqan_delimiter);

			tmp_scan_ptr = quote_x509_string( *fqan );
			strcat(&(retfqan[fqan_len]), tmp_scan_ptr);
			fqan_len += strlen( tmp_scan_ptr );
			free(tmp_scan_ptr);
		}

		*quoted_DN_and_FQAN = retfqan;
	}

	ret = 0;

end:
	free(subject_name);
	free(x509_fqan_delimiter);
	if (voms_data)
		(*VOMS_Destroy_ptr)(voms_data);

	return ret;
}
#endif

int
extract_VOMS_info( X509Credential* cred, int verify_type, char **voname, char **firstfqan, char **quoted_DN_and_FQAN)
{
	return extract_VOMS_info(cred->GetCert(), cred->GetChain(), verify_type, voname, firstfqan, quoted_DN_and_FQAN);
}

int
extract_VOMS_info_from_file( const char* proxy_file, int verify_type, char **voname, char **firstfqan, char **quoted_DN_and_FQAN)
{
	int rc = 1;
	X509Credential* cred = x509_proxy_read( proxy_file );

	if ( cred == NULL ) {
		return rc;
	}

	rc = extract_VOMS_info(cred->GetCert(), cred->GetChain(), verify_type, voname, firstfqan, quoted_DN_and_FQAN);

	delete cred;

	return rc;
}

/* Return the email of a given proxy cert.
  On error, return NULL.
  On success, return a pointer to a null-terminated string.
  IT IS THE CALLER'S RESPONSBILITY TO DE-ALLOCATE THE STIRNG
  WITH free().
 */

/* Return the subject name of a given proxy cert.
  On error, return NULL.
  On success, return a pointer to a null-terminated string.
  IT IS THE CALLER'S RESPONSBILITY TO DE-ALLOCATE THE STIRNG
  WITH free().
 */
char *
x509_proxy_subject_name( const char *proxy_file )
{
	char *subject_name = NULL;
	X509Credential* proxy_handle = x509_proxy_read( proxy_file );

	if ( proxy_handle == NULL ) {
		return NULL;
	}

	subject_name = x509_proxy_subject_name( proxy_handle );

	delete proxy_handle;

	return subject_name;
}

/* Return the identity name of a given X509 cert. For proxy certs, this
  will return the identity that the proxy can act on behalf of, rather than
  the subject of the proxy cert itself. Normally, this will have the
  practical effect of stripping off any "/CN=proxy" strings from the subject
  name.
  On error, return NULL.
  On success, return a pointer to a null-terminated string.
  IT IS THE CALLER'S RESPONSBILITY TO DE-ALLOCATE THE STIRNG
  WITH free().
 */
char *
x509_proxy_identity_name( const char *proxy_file )
{
	char *subject_name = NULL;
	X509Credential* proxy_handle = x509_proxy_read( proxy_file );

	if ( proxy_handle == NULL ) {
		return NULL;
	}

	subject_name = x509_proxy_identity_name( proxy_handle );

	delete proxy_handle;

	return subject_name;
}

/* Return the time at which the proxy expires. On error, return -1.
 */
time_t
x509_proxy_expiration_time( const char *proxy_file )
{
	time_t expiration_time = -1;
	X509Credential* proxy_handle = x509_proxy_read( proxy_file );

	if ( proxy_handle == NULL ) {
		return -1;
	}

	expiration_time = x509_proxy_expiration_time( proxy_handle );

	delete proxy_handle;

	return expiration_time;
}

static int
buffer_to_bio( char *buffer, size_t buffer_len, BIO **bio )
{
	if ( buffer == NULL ) {
		return FALSE;
	}

	*bio = BIO_new( BIO_s_mem() );
	if ( *bio == NULL ) {
		return FALSE;
	}

	if ( BIO_write( *bio, buffer, buffer_len ) < (int)buffer_len ) {
		BIO_free( *bio );
		return FALSE;
	}

	return TRUE;
}

static int
bio_to_buffer( BIO *bio, char **buffer, size_t *buffer_len )
{
	if ( bio == NULL ) {
		return FALSE;
	}

	*buffer_len = BIO_pending( bio );

	*buffer = (char *)malloc( *buffer_len );
	if ( *buffer == NULL ) {
		return FALSE;
	}

	if ( BIO_read( bio, *buffer, *buffer_len ) < (int)*buffer_len ) {
		free( *buffer );
		return FALSE;
	}

	return TRUE;
}

int
x509_send_delegation( const char *source_file,
					  time_t expiration_time,
					  time_t *result_expiration_time,
					  int (*recv_data_func)(void *, void **, size_t *),
					  void *recv_data_ptr,
					  int (*send_data_func)(void *, void *, size_t),
					  void *send_data_ptr )
{
	int rc = -1;
	char *buffer = nullptr;
	size_t buffer_len = 0;
	BIO *in_bio = nullptr;
	BIO *out_bio = nullptr;
	X509 *src_cert = nullptr;
	STACK_OF(X509) *src_chain = nullptr;
	bool did_send = false;
	DelegationRestrictions restrict;
	X509Credential deleg_provider(source_file, "");

	if ( recv_data_func( recv_data_ptr, (void **)&buffer, &buffer_len ) != 0 || buffer == NULL ) {
		_globus_error_message = "Failed to receive delegation request";
		goto cleanup;
	}

	if ( buffer_to_bio( buffer, buffer_len, &in_bio ) == FALSE ) {
		_globus_error_message = "buffer_to_bio() failed";
		goto cleanup;
	}

	free(buffer);
	buffer = nullptr;

	if ( ! param_boolean("DELEGATE_FULL_JOB_GSI_CREDENTIALS", false) ) {
		restrict["policyLimited"] = "true";
	}

	src_cert = deleg_provider.GetCert();
	src_chain = deleg_provider.GetChain();
	if ( ! src_cert ) {
		_globus_error_message = "Failed to read proxy file";
		goto cleanup;
	}

	if ( expiration_time || result_expiration_time ) {
		time_t orig_expiration_time = x509_proxy_expiration_time(src_cert, src_chain);

		if( expiration_time && orig_expiration_time > expiration_time ) {
			restrict["validityEnd"] = std::to_string(expiration_time);
		}

		if( result_expiration_time ) {
			*result_expiration_time = expiration_time;
		}
	}

	out_bio = deleg_provider.Delegate(in_bio, restrict);
	if ( ! out_bio ) {
		_globus_error_message = "X509Credential::Delegate() failed";
		goto cleanup;
	}

	if ( bio_to_buffer( out_bio, &buffer, &buffer_len ) == FALSE ) {
		_globus_error_message = "bio_to_buffer() failed";
		goto cleanup;
	}

	did_send = true;
	if ( send_data_func( send_data_ptr, buffer, buffer_len ) != 0 ) {
		_globus_error_message = "Failed to send delegated proxy";
		goto cleanup;
	}

	rc = 0;

 cleanup:
	if ( ! did_send ) {
		send_data_func( send_data_ptr, NULL, 0 );
	}
	if ( buffer ) {
		free( buffer );
	}
	if ( in_bio ) {
		BIO_free( in_bio );
	}
	if ( out_bio ) {
		BIO_free( out_bio );
	}

	return rc;
}

struct x509_delegation_state
{
	std::string m_dest;
	X509Credential m_deleg_consumer;
};

int
x509_receive_delegation( const char *destination_file,
						 int (*recv_data_func)(void *, void **, size_t *),
						 void *recv_data_ptr,
						 int (*send_data_func)(void *, void *, size_t),
						 void *send_data_ptr,
						 void ** state_ptr )
{
	int rc = -1;
	x509_delegation_state *st = new x509_delegation_state();
	st->m_dest = destination_file;
	char *buffer = NULL;
	size_t buffer_len = 0;
	BIO *bio = NULL;
	bool did_send = false;

		// make cert request
	bio = BIO_new( BIO_s_mem() );
	if ( bio == nullptr ) {
		_globus_error_message = "BIO_new() failed";
		goto cleanup;
	}

	if ( ! st->m_deleg_consumer.Request(bio) ) {
		_globus_error_message = "X509Credential::Request() failed";
		goto cleanup;
	}

	if ( bio_to_buffer( bio, &buffer, &buffer_len ) == FALSE ) {
		_globus_error_message = "bio_to_buffer() failed";
		goto cleanup;
	}

	did_send = true;
	if ( send_data_func( send_data_ptr, buffer, buffer_len ) != 0 ) {
		_globus_error_message = "Failed to send delegation request";
		goto cleanup;
	}

	rc = 0;

 cleanup:
	if ( !did_send ) {
		send_data_func( send_data_ptr, NULL, 0 );
	}
	if ( bio ) {
		BIO_free( bio );
	}
	if ( buffer ) {
		free( buffer );
	}

	// Error!  Cleanup memory immediately and return.
	if ( rc ) {
		delete st;
		return rc;
	}

	// We were given a state pointer - caller will take care of monitoring the
	// socket for more data and call delegation_finish later.
	if (state_ptr != NULL) {
		*state_ptr = st;
		return 2;
	}

	// Else, we block and finish up immediately.
	return x509_receive_delegation_finish(recv_data_func, recv_data_ptr, st);
}

// Finish up the delegation operation, waiting for data on the socket if necessary.
// NOTE: get_x509_delegation_finish will take ownership of state_ptr and free its
int x509_receive_delegation_finish(int (*recv_data_func)(void *, void **, size_t *),
                               void *recv_data_ptr,
                               void *state_ptr_raw)
{
	int rc = -1;
	x509_delegation_state *state_ptr = static_cast<x509_delegation_state*>(state_ptr_raw);
	char *buffer = nullptr;
	size_t buffer_len = 0;
	BIO *bio = nullptr;
	std::string proxy_contents;
	std::string proxy_subject;
	int fd = -1;

	if ( recv_data_func( recv_data_ptr, (void **)&buffer, &buffer_len ) != 0 || buffer == NULL ) {
		_globus_error_message = "Failed to receive delegated proxy";
		goto cleanup;
	}

	if ( buffer_to_bio( buffer, buffer_len, &bio ) == FALSE ) {
		_globus_error_message = "buffer_to_bio() failed";
		goto cleanup;
	}

	if ( ! state_ptr->m_deleg_consumer.Acquire(bio, proxy_contents, proxy_subject) ) {
		_globus_error_message = "X509Credential::Acquire() failed";
		goto cleanup;
	}

		// write proxy file
		// safe_open O_WRONLY|O_EXCL|O_CREAT, S_IRUSR|S_IWUSR
	fd = safe_open_wrapper_follow(state_ptr->m_dest.c_str(), O_WRONLY|O_EXCL|O_CREAT, 0600);
	if ( fd < 0 ) {
		_globus_error_message = "Failed to open proxy file";
		goto cleanup;
	}

	if ( write(fd, proxy_contents.c_str(), proxy_contents.size()) < (ssize_t)proxy_contents.size() ) {
		_globus_error_message = "Failed to write proxy file";
		goto cleanup;
	}

	rc = 0;

 cleanup:
	if ( bio ) {
		BIO_free( bio );
	}
	if ( buffer ) {
		free( buffer );
	}
	if ( state_ptr ) {
		delete state_ptr;
	}
	if ( fd >= 0 ) {
		close(fd);
	}

	return rc;
}


void UPnpServiceImpl::RegisterService(UPnpDevice *pDevice)
{
    if (pDevice != nullptr)
    {
        auto *pService = new UPnpService();

        pService->m_sServiceType = GetServiceType();
        pService->m_sServiceId   = GetServiceId();
        pService->m_sSCPDURL     = GetServiceDescURL();
        pService->m_sControlURL  = GetServiceControlURL();
        pService->m_sEventSubURL = GetServiceEventURL();

        pDevice->m_listServices.push_back(pService);
    }
}

#include "upnpserviceimpl.h"
#include "upnpdevice.h"

/// Creates a UPnpService and adds it to the UPnpDevice's list of services.

QString UPnPFeature::toXML()
{
    QString xml;
    xml = "<Feature name=\"" + m_name + "\" version=\"" + QString::number(m_version) + "\">\r\n";
    xml += CreateXML();
    xml += "</Feature>\r\n";
    return xml;
}

UPnPFeatureList::~UPnPFeatureList()
{
    while (!m_features.isEmpty())
        delete m_features.takeFirst();
}

void UPnPFeatureList::AddFeature(UPnPFeature *feature)
{
    m_features.append(feature);
}

void UPnPFeatureList::AddAttribute(const NameValue& attribute)
{
    m_attributes.append(attribute);
}

QString UPnPFeatureList::toXML()
{
    QString xml;

    xml = "<Features ";
    NameValues::iterator ait;
    for (ait = m_attributes.begin(); ait != m_attributes.end(); ++ait)
    {
        xml += QString(" %1=\"%2\"").arg((*ait).m_sName, (*ait).m_sValue);
    }
    xml += ">\r\n";
    QList<UPnPFeature*>::iterator fit;
    for (fit = m_features.begin(); fit != m_features.end(); ++fit)
    {
        xml += "    " + (*fit)->toXML();
    }
    xml += "</Features>\r\n";
    return xml;
}